openGL中GLSL语言介绍

GLSL 语言概念介绍

内容翻译至GLSLangSpec.1.20,已尽量通顺,随便看看吧

介绍

  1. 概述

    用这种语言编写的独立编译单元称为着色器。一个程序是一组完整的着色器,它们被编译和链接在一起。本文档的目的是详细说明编程语言。用于操作和与程序和着色器通信的OpenGL入口点在一个单独的规范中定义。

    1. 错误处理

    通常,编译器接受格式不良的程序,因为不可能检测到所有格式不良的程序。可移植性只保证了格式良好的程序,这是本规范所描述的。鼓励编译器检测格式不良的程序并发出诊断消息,但并不是所有情况下都必须这样做。编译器必须返回关于词汇、语法或语义上不正确的着色器的消息。

OpenGL着色的概述

OpenGL着色器语言实际上是两种密切相关的语言。这些语言被用来为包含在OpenGL处理管道中的可编程处理器创建着色器。

除非在本文中另有说明,否则一种语言特性适用于所有语言,通常将这些语言称为单一语言。特定的语言将通过它们目标处理器的名称来引用:顶点或片段。

着色器使用的任何OpenGL状态都会被自动跟踪并提供给着色器。这种自动状态跟踪机制允许应用程序使用现有的OpenGL状态命令进行状态管理,并使这种状态的当前值自动可用在着色器中。

  1. 顶点处理器

    顶点处理器是一个可编程的单元,它对传入的顶点及其相关数据进行操作。用OpenGL着色语言编写的在这个处理器上运行的编译单元称为顶点着色器。当一组完整的顶点着色器被编译和链接时,它们会产生一个在顶点处理器上运行的顶点着色器可执行文件。

    顶点处理器一次只处理一个顶点。它不会取代一次需要几个顶点知识的图形操作。在顶点处理器上运行的顶点着色器必须计算传入顶点的同构位置。

  2. 片段处理器

    片段处理器是一个可编程的单元,对片段值及其相关数据进行操作。用OpenGL着色语言编写的在这个处理器上运行的编译单元称为片段着色器。当一组完整的片段着色器被编译和链接时,它们会产生一个片段着色器可执行程序,在片段处理器上运行。

    一个片段着色器不能改变片段的x/y位置。不允许访问相邻的片段。由片段着色器计算的值最终用于更新帧缓冲内存或纹理内存,取决于当前OpenGL状态和OpenGL命令,导致片段被生成。

基础知识

  1. 字符集

    用于OpenGL着色语言的源字符集是ASCII的一个子集。它包括以下字符:

    ​ 字母a-z, a-z和下划线(_)

    ​ 0 - 9的数字

    ​ 符号包括句点(.)、加号(+)、破折号(-)、斜杠(/)、星号(*)、百分比(%)、尖括号(<和))、方括号([和])、圆括号((和))

    大括号({和})、插入符号(^)、竖条(|)、符号(&)、波浪号(~)、等号(=)、感叹号(!)、冒号(😉、分号(😉、逗号(,)和问号(?)

    ​ 用于预处理器的数字符号(#)

    ​ 空格:空格字符、水平制表符、垂直制表符、换行符、回车符和换行符。

    行与编译器诊断消息和预处理器相关。它们通过回车或换行来终止。如果两者一起使用,它将被认为只有一个单行终止。对于本文档的其余部分,任何这些组合都称为新行。没有行继续符。

    通常,该语言对这个字符集的使用是区分大小写的。

    没有字符或字符串数据类型,因此不包括引号字符。

    没有文件结束字符。源字符串的结束是由长度而不是字符来表示的。

  2. 源字符串

    单个着色器的源是来自字符集的字符串数组。一个单一的着色器是由这些字符串的串联组成的。每个字符串可以包含多行,由新行分隔。字符串中不需要出现新行;可以由多个字符串组成一行。当实现连接字符串以形成单个着色器时,不会插入新行或其他字符。多个着色器可以链接在一起形成一个单一的程序。

    从编译着色器返回的诊断消息必须识别字符串中的行号和消息应用于哪个源字符串。源字符串按顺序计数,第一个字符串为字符串0。行号比已处理的新行数多1。

  3. 预处理器

    作为编译过程的一部分,有一个预处理程序处理源字符串。

    下面是预处理器指令的完整列表。

    #
    #define
    #undef
    #if
    #ifdef
    #ifndef
    #else
    #elif
    #endif
    #error
    #pragma
    #extension 
    #version 
    #line
    

    还可以使用以下操作符 defined

    每个数字符号(#)只能在该行前面加上空格或水平制表符。它也可以后跟空格和水平制表符,在指令之前。每个指令被一个新行终止。预处理不会改变源字符串中新行的数量或相对位置。

    行上的数字符号(#)被忽略。任何没有列在上面的指令将导致一个诊断消息,并使实现对待着色作为病态的格式。

    对于带有或不带有宏参数的宏定义,#define和#undef功能是c++预处理器的标准定义。

    可以使用以下预定义的宏

    __LINE__
    __FILE__
    __VERSION__
    

    __LINE__将替换一个十进制整数常数,该常数比当前源字符串中前面的新行数多1。

    __FILE__将替换一个十进制整数常量,表示当前正在处理哪个源字符串。

    __VERSION__将用十进制整数代替OpenGL着色语言的版本号。本文档中描述的着色语言版本将用剩余版本__替代十进制整数120。

    所有包含两个连续的下划线(__)的宏名称被保留为将来使用预定义的宏名称。所有前缀为“GL_”(“GL”后跟一个下划线)的宏名称也是保留的。

    #if、#ifdef、#ifndef、#else、#elif和#endif被定义为按照c++预处理器的标准操作。#if和#elif后面的表达式仅限于对整数值常量进行操作的表达式,加上定义的操作符使用的标识符。不支持字符常量。可用的操作符如下。

    优先级算符分类操作符相关性
    1(height)括号里的分组( )NA
    2单运算元defined + - ~ !右到左
    3乘法的* / %左到右
    4递增法+ -左到右
    5位操作的转变<< >>左到右
    6相关的< > <= >=左到右
    7等于== !=左到右
    8位操作&左到右
    9逐位互斥或^左到右
    10位操作包容或|左到右
    11逻辑与&&左到右
    12(lowest)逻辑包容或||左到右

    定义的运算符可以用以下任何一种方式使用:

    defined identifier
    defined ( identifier )
    

    没有基于数字符号的操作符(没有#、#@、##等),也没有sizeof操作符。

    在预处理程序中对整型文字应用运算符的语义与c++预处理程序中的标准匹配,而不是与OpenGL着色语言中的标准匹配。

    预处理器表达式将根据主机处理器的行为来计算,而不是根据着色器的目标处理器来计算。

    #error 将导致实现放入一个诊断消息到着色对象的信息日志(参阅API在外部文档中如何访问着色对象的信息日志)。消息将是 #error 指令之后的标记,直到第一个新行。该实现必须考虑着色器是病态的。

    #pragma 允许依赖于实现的编译器控件。#pragma后面的标记不受预处理器宏扩展的影响。如果实现不能识别下面的令牌#pragma,那么它会忽略那个pragma。下面的pragmas被定义为该语言的一部分。

    #pragma STDGL
    

    STDGL pragma用来为该语言的未来修订保留pragma。任何实现都不能使用第一个令牌是STDGL的pragma。

    #pragma optimize(on)
    #pragma optimize(off)
    

    上述的可以用来关闭优化作为开发和调试着色器的帮助。它只能在函数定义之外使用。默认情况下,优化是为所有着色器打开的。

    #pragma debug(on)
    #pragma debug(off)
    

    上述的可用于启用编译和注释带有调试信息的着色器,以便它可以与调试器一起使用。它只能在函数定义之外使用。默认情况下,调试是关闭的。

    着色器应该声明它们被写入的语言版本。一个着色器被写入的语言版本被指定 #version number ,如果数字必须是语言的一个版本,遵循与上面的_VERSION__相同的约定。

    指令“#version 120”在任何使用1.20版本语言的着色器中都是必需的。表示编译器不支持的语言版本的任何数字都将导致生成错误。版本1.10的语言不需要着色器包括这个指令,不包括#版本指令的着色器将被视为目标版本1.10。针对1.10版本的着色器的行为不会受到1.20版本中引入的任何变化的影响。在同一程序中链接在一起的不同着色器(编译单元)不必有相同的版本;它们可以是1.10和1.20版本着色器的混合。

    #version指令必须出现在着色器中除注释和空白之外的任何东西之前。

    默认情况下,此语言的编译器必须对不符合此规范的着色器发出编译时语法、语法和语义错误。必须首先启用任何扩展行为。属性声明用于控制编译器对以下扩展的行为的指令#扩展指令

    #extension extension_name : behavior
    #extension all : behavior
    

    此规范中没有记录扩展名。令牌意味着行为应用于编译器支持的所有扩展。以下是一些行为介绍

    behaviorEffect
    require按扩展名extension_name指定的方式运行。如果不支持扩展名extension_name,或者指定了全部,则在#扩展名上给出一个错误。
    enable按扩展名extension_name指定的方式运行。如果不支持扩展名extension_name,请在#扩展名上发出警告。如果全部都指定了,则在#扩展名上给出一个错误
    warn行为如扩展extension_name所指定的那样,除非对该扩展的任何可检测使用发出警告,除非其他已启用或需要的扩展支持这种使用。如果所有被指定,然后警告所有可检测的任何扩展使用。如果不支持扩展名extension_name,请在#扩展名上发出警告。
    disable行为(包括发出错误和警告),就好像扩展extension_name不是语言定义的一部分。 如果全部都指定了,那么行为必须恢复到正在编译的语言的非扩展核心版本。

    extension指令是一种简单的低级机制,用于设置每个扩展的行为。它没有定义策略,比如哪些组合是合适的,这些必须在其他地方定义。在设置每个扩展的行为时,指令的顺序很重要:稍后出现的指令会覆盖前面看到的那些指令。all变量为所有扩展设置行为,覆盖之前发布的所有扩展指令,但只针对警告和禁用行为。

    编译器的初始状态就像指令一样
    #extension all: disable被发出,告诉编译器所有的错误和警告报告必须根据这个规范来完成,忽略任何扩展。

    每个扩展都可以定义其允许的范围粒度。如果什么都没说,粒度就是一个着色器(也就是说,一个编译单元),扩展指令必须出现在任何非预处理器标记之前。如果有必要,链接器可以强制粒度大于单个编译单元,在这种情况下,每个涉及的着色器将不得不包含必要的扩展指令。

    宏扩展不会在包含#extension和#version指令的行上完成。

    #行必须,在宏替换后,以下两种形式之一:

    #line line
    #line line source-string-number
    

    其中行和源字符串数是常量整数表达式。在处理这个指令(包括它的新行)之后,实现的行为就像它在行号行+1处编译,并且源字符串的编号是源-字符串-数字。后续的源字符串将按顺序编号,直到另一个#line指令覆盖了该编号。

  4. 注解

    注释由//分隔,或由//和换行分隔。不能将开始注释分隔符(/*或//)识别为注释中的注释分隔符,因此不能嵌套注释。如果注释完全位于一行中,则语法上将其视为单个空格。注释不会消除新行。

  5. 符号

    该语言是一个标记序列。

    token:
    keyword identifier integer-constant
    floating-constant
    operator
    ; { }
    
  6. 关键字

    以下是该语言中的关键字,只能用于本文件定义的其他目的:

     attribute const uniform varying
     centroid
     break continue do for while
     if else
     in out inout
     float int void bool true false
     invariant
     discard return
     mat2 mat3 mat4
     mat2x2 mat2x3 mat2x4
     mat3x2 mat3x3 mat3x4
     mat4x2 mat4x3 mat4x4
     vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4
     sampler1D sampler2D sampler3D samplerCube 
     sampler1DShadow sampler2DShadow
     struct
    

    以下是保留以备将来使用的关键字。使用它们会导致错误

     asm
     class union enum typedef template this packed
     goto switch default
     inline noinline volatile public static extern external interface
     long short double half fixed unsigned
     lowp mediump highp precision
     input output
     hvec2 hvec3 hvec4 dvec2 dvec3 dvec4 fvec2 fvec3 fvec4
     sampler2DRect sampler3DRect sampler2DRectShadow
     sizeof cast
     namespace using
    

    此外,所有包含两个连续的下划线(__)的标识符被保留为可能的未来关键字。

  7. 标识符

    标识符用于变量名、函数名、结构名和字段选择器(字段选择器选择向量和矩阵的组件,类似于结构字段)。标识符的形式为

    identifier
    nondigit identifier nondigit identifier digit
    nondigit: one of _ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
    digit: one of 0 1 2 3 4 5 6 7 8 9
    

    以“gl_”开头的标识符是保留给OpenGL使用的,并且不能在着色器中作为变量或函数声明。

变量和类型

所有变量和函数在使用前都必须声明。变量和函数名是标识符。

没有默认类型。所有变量和函数声明都必须有已声明的类型和可选限定符。变量的声明方式是,在变量后面指定一个或多个用逗号分隔的名称。在许多情况下,可以通过使用赋值操作符(=)将变量初始化为其声明的一部分。

用户定义类型可以使用struct来定义,以将现有类型列表聚合为单个名称。

OpenGL着色器语言是类型安全的。类型之间不存在隐式转换,除非需要浮点类型的地方可能出现整数值,并将其转换为浮点值。

  1. 基本类型

    OpenGL着色器语言支持以下基本数据类型。

    类型含义
    void用于不返回值的函数
    bool一种条件类型,其值为真或假
    int有符号整数
    float单个浮点标量
    vec2两个组成的浮点向量
    vec3由三部分组成的浮点向量
    vec4由四部分组成的浮点向量
    bvec2一个双分量布尔向量
    bvec3一个三分量布尔向量
    bvec4一个四分量布尔向量
    ivec2一个两分量的整数向量
    ivec3一个三分量的整数向量
    ivec4一个四分量的整数向量
    mat2一个2×2浮点矩阵
    mat3一个3×3浮点矩阵
    mat4一个4×4浮点矩阵
    mat2x2一个2×2浮点矩阵
    mat2x3一种2列3行的浮点矩阵
    mat2x4一种2列4行的浮点矩阵
    mat3x23列2行的浮点矩阵
    mat3x3一个3×3浮点矩阵
    mat3x43列4行的浮点矩阵
    mat4x2一种4列2行的浮点矩阵
    mat4x3一种4列3行的浮点矩阵
    mat4x4一个4×4浮点矩阵
    sampler1D用于访问一维纹理的句柄
    sampler2D用于访问2D纹理的句柄
    sampler3D用于访问3D纹理的句柄
    samplerCube用于访问多维数据集映射纹理的句柄
    sampler1DShadow一个比较访问一维深度纹理的句柄
    sampler2DShadow一个比较访问二维深度纹理的句柄

    另外,着色器可以使用数组和结构来聚合这些类型,以构建更复杂的类型。没有指针类型。

    1. Void

      不返回值的函数必须声明为void。没有默认的函数返回类型。关键字void不能用于任何其他声明(除了空的形式参数列表)。

    2. Booleans

      为了使代码的条件执行更容易表达,支持bool类型。不期望硬件直接支持这种类型的变量。它是一个真正的布尔类型,只包含两个值中的一个,表示真或假。两个关键字true和false可以用作文字布尔常量。布尔值的声明和可选初始化如下示例:

      bool success; // declare “success” to be a Boolean
      bool done = false; // declare and initialize “done”
      

      赋值操作符(=)的右侧可以是任何类型为bool的表达式。

      用于条件跳转的表达式(if, for, ?:, while, do-while)必须计算为bool类型。

    3. Integers

      整数主要是作为编程辅助工具而被支持的。在硬件级别,实整数将有助于有效地实现循环和数组索引,以及引用纹理单元。但是,不要求语言中的整数映射到硬件中的整数类型。底层硬件不应该完全支持广泛的整数操作。由于它们的目的(有限的),整数的精度被限制在16位,在顶点语言和片段语言中都加上符号表示。OpenGL着色语言实现可以将整数转换为浮点数来对它们进行操作。允许实现使用超过16位的精度来操作整数。因此,没有可移植的包装行为。溢出16位精度的着色器可能不能移植。

      如下面的示例所示,使用整数表达式声明整数并可选地初始化整数

      int i, j = 42;
      

      文字整型常量可以用十进制(以10为基数)、八进制(以8为基数)或十六进制(以16为基数)表示,如下所示

      integer-constant :
      decimal-constant
      octal-constant
      hexadecimal-constant
      decimal-constant :
      nonzero-digit
      decimal-constant digit
      octal-constant : 0
      octal-constant octal-digit
      hexadecimal-constant : 0x hexadecimal-digit
      0X hexadecimal-digit
      hexadecimal-constant hexadecimal-digit
      digit :0
      nonzero-digit
      nonzero-digit : one of 1 2 3 4 5 6 7 8 9
      octal-digit : one of 0 1 2 3 4 5 6 7
      hexadecimal-digit : one of 0 1 2 3 4 5 6 7 8 9 a b c d e f
      A B C D E F
      

      整数常数的位数之间不允许有空格,包括常数的前导0之后或前导0x或0x之后。前导一元减号(-)被解释为算术一元否定,而不是常数的一部分。没有字母后缀。

    4. Floats

      浮点数可用于各种标量计算。浮点变量定义如下示例:

      float a, b = 1.5;
      

      作为一个处理单元的输入值,浮点变量需要符合IEEE单精度浮点定义的精度和动态范围。并不要求内部处理的精度与用于浮点操作的IEEE浮点规范相匹配,但必须满足OpenGL 1.4规范建立的精度准则。类似地,对条件(如除以0)的处理可能导致未指定的结果,但在任何情况下,这样的条件都不应该导致中断或终止处理。

      浮点常量的定义如下。

      floating-constant : fractional-constant exponent-partopt 
      floating-suffixopt
      digit-sequence exponent-part floating-suffixopt fractional-constant :
      digit-sequence . digit-sequence
      digit-sequence .
      . digit-sequence exponent-part :
      e signopt
       digit-sequence E signopt
       digit-sequence sign : one of + –
      digit-sequence :
      digit
      digit-sequence digit floating-suffix: one of f F
      

      小数点如果有指数部分,则不需要. 浮点常量的任何位置都不能出现空格。前导一元减号(-)被解释为一元运算符,不属于浮点常数

    5. Vectors

      OpenGL着色语言包括浮点值、整数或布尔值的泛型2、3和4分量向量的数据类型。浮点向量变量可以用来存储在计算机图形中非常有用的各种东西:颜色、法线、位置、纹理坐标、纹理查找结果等等。布尔向量可用于数值向量的组件级比较。将向量定义为着色器语言的一部分,允许在图形硬件上直接映射向量操作,从而能够进行向量处理。通常,应用程序可以通过在向量上而不是在标量上计算来更好地利用图形硬件中的并行性。向量声明的一些例子:

      vec2 texcoord1, texcoord2;
      vec3 position;
      vec4 myRGBA;
      ivec2 textureLookup;
      bvec3 less;
      

      向量的初始化可以用构造函数来完成,稍后将讨论构造函数。

    6. Matrices

      OpenGL底纹语言内置了2×2、2×3、2×4、3×2、3×3、3×4、4×2、4×3和4×4个浮点数矩阵的类型。类型中的第一个数字是列数,第二个是行数。示例矩阵声明

      mat2 mat2D;
      mat3 optMatrix;
      mat4 view, projection;
      mat4x4 view; // an alternate way of declaring a mat4
      mat3x2 m; // a matrix with 3 columns and 2 rows
      

      矩阵值的初始化是用构造函数按列主顺序完成的.

    7. Samplers

      采样器类型(例如sampler2D)是纹理的有效不透明处理。它们与内建纹理函数一起用于指定要访问的纹理。它们只能声明为函数参数或统一变量。除了数组索引、结构字段选择和括号外,采样器不允许作为表达式中的操作数。采样器不能作为l值处理;因此不能用作out或inout函数参数,也不能将它们分配给。作为uniforms,它们只被OpenGL API初始化;它们不能在着色器中使用初始化器声明。作为函数参数,只能将采样器传递给匹配类型的采样器。这使得在一个着色器运行之前,可以检查着色器纹理访问和OpenGL纹理状态之间的一致性。

    8. Structures

      用户定义的类型可以通过使用struct关键字将其他已经定义的类型聚合到一个结构中来创建。例如

      struct light {
       float intensity;
       vec3 position;
      } lightVar;
      

      在本例中,light成为新类型的名称,而lightVar成为light类型的变量。要声明新类型的变量,请使用它的名称(不使用关键字struct)。

      light lightVar2;
      

      更正式地,结构声明如下。然而,完整正确的语法“着色器语言语法”所示。

      struct-definition :
      qualifieropt
       struct nameopt { member-list } declaratorsopt
       ;
      member-list :
      member-declaration;
      member-declaration member-list; member-declaration :
      basic-type declarators;
      

      其中name成为用户定义类型,并可用于将变量声明为这种新类型。该名称与其他变量、类型和函数共享相同的名称空间,并具有相同的作用域规则。可选限定符只适用于任何声明者,不属于为name定义的类型。

      结构必须至少有一个成员声明。成员声明符不包含任何限定符。它们也不包含任何位字段。成员类型必须已经定义(没有转发引用)。成员声明不能包含初始值设定项。成员声明符可以包含数组。这样的数组必须指定大小,并且大小必须是大于零的整型常量表达式。每一层结构都有自己的名称空间,用于成员声明者中给出的名称;这些名称只需要在名称空间中唯一。

      不支持匿名结构。不支持嵌入式结构。

      struct S { float f; };
      struct T {
      S; // Error: anonymous structures disallowed
      struct { ... }; // Error: embedded structures disallowed
      S s; // Okay: nested structures with name are allowed
      };
      

      结构可以在声明时使用构造函数初始化

    9. Arrays

      通过声明一个名称后加上括号([]),可以将相同类型的变量聚合为数组。当在声明中指定数组大小时,它必须是大于零的整型常量表达式。如果数组使用一个不是整型常量表达式的表达式作为索引,或者数组作为参数传递给函数,那么在使用之前必须声明它的大小。可以合法地声明没有大小的数组,然后将相同的名称重新声明为相同类型的数组,并指定大小。声明一个有大小的数组,然后(在同一个着色器中)用大于或等于声明大小的整型常量表达式索引同一个数组是非法的。使用负常数表达式为数组建立索引也是非法的。在函数声明中作为形式参数声明的数组必须指定大小。未定义的行为是由使用大于或等于数组大小或小于0的非常量表达式为数组索引而产生的。只能声明一维数组。所有基本类型和结构都可以组成数组。一些示例:

      float frequencies[3];
      uniform vec4 lightPosition[4];
      light lights[];
      const int numLights = 2;
      light lights[numLights];
      

      数组类型可以通过指定类型后加上方括号([])并包含大小来形成:

      float[5]
      

      此类型可以在任何其他类型可以使用的地方使用,包括作为函数的返回值

      float[5] foo() { }
      

      作为数组的构造函数

      float[5](3.4, 4.2, 5.0, 5.2, 1.1)
      

      作为未命名参数

      void foo(float[5])
      

      以及作为声明变量或函数参数的另一种方法。

      float[5] a;
      

      它是一个错误声明数组的数组:

      float a[5][3]; // illegal
      float[5] a[3]; // illegal
      

      数组可以由数组构造函数组成初始值设定项:

      float a[5] = float[5](3.4, 4.2, 5.0, 5.2, 1.1);
      float a[5] = float[](3.4, 4.2, 5.0, 5.2, 1.1); // same thing
      

      未调整大小的数组可以在声明时由初始化器显式调整大小:

      float a[5];
      ...
      float b[] = a; // b is explicitly size 5
      float b[5] = a; // means the same thing
      

      但是,不能将隐式大小数组分配给。注意,初始化项和赋值项具有不同语义的情况很少见。

      不能对未显式调整大小的数组调用length方法。

    10. Implicit Conversions

      在某些情况下,表达式及其类型将隐式转换为另一种类型。下表显示了所有允许的隐式转换:

    | **Type of expression** | **Can be implicitly converted to** |
    | :--------------------: | :--------------------------------: |
    |        **int**         |             **float**              |
    |       **ivec2**        |              **vec2**              |
    |       **ivec3**        |              **vec3**              |
    |       **ivec4**        |              **vec4**              |
    
     没有隐式数组或结构转换。例如,int数组不能隐式转换为float数组。
    
     当隐式转换完成时,它不只是重新解释表达式的值,而是将该值转换为新类型中的等效值。例如,整数值5将被转换为浮点值5.0。
    
     上表中的转换仅按照本规范其他部分的指示进行。
    
  2. 作用域

    变量的作用域由声明它的位置决定。如果它是在所有函数定义之外声明的,它就有全局作用域,它从声明它的地方开始,一直持续到声明它的着色器的末尾。如果它是在while测试或for语句中声明的,那么它的作用域将限定在以下子语句的末尾。否则,如果它被声明为复合语句中的一条语句,那么它的作用域将限定在该复合语句的末尾。如果它在函数定义中被声明为参数,那么它的作用域将一直到该函数定义结束。函数体有一个嵌套在函数定义中的作用域。if语句的表达式不允许声明新变量,因此不形成新范围。

    在声明中,如果存在名称,则名称的作用域立即从初始化器之后开始;如果没有,则立即从声明的名称之后开始。几个例子:

    int x = 1;
    { 
    int x = 2, y = x; // y is initialized to 2
    }
    struct S 
    { 
    int x; 
    };
    {
    S S = S(0,0); // 'S' is only visible as a struct and constructor 
    S; // 'S' is now visible as a variable
    }
    int x = x; // Error if x has not been previously defined.
    

    给定作用域中的所有变量名、结构类型名和函数名共享相同的名称空间。

    可以使用相同或不同的参数在相同作用域中重新声明函数名,而不会出现错误。

    可以在相同范围内将隐式大小的数组与具有相同基类型的数组重新声明。

    否则,在一个编译单元内,不能在同一作用域中重新声明已声明的名称;这样做会导致重新声明错误。如果嵌套作用域重新声明在外部作用域中使用的名称,它将隐藏该名称的所有现有用法。在不退出隐藏名称的作用域的情况下,无法访问隐藏名称或使其不隐藏。

    内置函数的作用域在用户声明全局变量的全局作用域之外。也就是说,着色器的全局作用域,可用于用户定义的函数和全局变量,嵌套在包含内置函数的作用域内。当在嵌套作用域中重声明函数名时,它会隐藏在外部作用域中用该名称声明的所有函数。函数声明(原型)不能出现在函数内部;它们必须在全局作用域内,或者对于内置函数来说,在全局作用域之外。

    共享全局变量是在同一语言(顶点或片段)的独立编译单元(着色器)中以相同名称声明的全局变量,它们链接在一起构成一个单独的程序。共享全局变量共享相同的名称空间,并且必须使用相同的类型声明。它们将共享相同的存储。共享全局数组必须具有相同的基类型和相同的显式大小。在一个着色器中隐式调整大小的数组可以被另一个着色器显式调整大小。如果没有着色器为数组指定明确的大小,则使用最大的隐式大小。标量必须具有完全相同的类型名称和类型定义。结构必须具有相同的名称、类型名称序列和类型定义,以及被视为相同类型的字段名称。此规则递归应用于嵌套或嵌入类型。共享全局变量的所有初始化项必须具有相同的值,否则将导致链接错误。

  3. 存储修饰符

    变量声明可以在类型前面指定一个存储修饰符。如图:

    修饰符含义
    < none: default >本地读/写内存,或一个函数的输入参数
    const只读的编译时常数或函数参数
    attribute每个顶点数据的顶点着色器和OpenGL之间的链接
    uniform值不会在被处理的原语之间改变,统一形成了着色器、OpenGL和应用程序之间的链接
    varying centroid varying为插值数据的顶点着色器和片段着色器之间的链接

    全局变量只能使用const、attribute、uniform、varying或centroid varying。只能指定一个。

    局部变量只能使用const存储限定符。

    函数参数只能使用const存储限定符。

    函数返回类型和结构字段不使用存储限定符。

    用于从着色器可执行文件的一次运行到下一次运行(在片段之间或顶点之间进行通信)的数据类型不存在。这将防止在多个顶点或片段上并行执行相同的着色器可执行。

    初始化器只能用于声明没有存储限定符、const限定符或uniform限定符的全局变量。没有存储限定符的全局变量如果在声明中没有初始化,或者应用程序没有初始化,那么OpenGL不会初始化它们,而是会输入带有未定义值的main()。

    1. Default Storage Qualifier

      如果一个全局变量上没有限定符,那么这个变量就没有链接到运行在其他处理器上的应用程序或着色器。无论是全局变量还是局部非限定变量,声明都会显示为分配与其目标处理器相关联的内存。这个变量将提供对这个已分配内存的读/写访问。

    2. Const

      可以使用const限定符声明已命名的编译时常量。任何被限定为常量的变量都是该着色器的只读变量。声明变量为常量比使用硬连接的数值常量允许更多的描述性着色器。const限定符可以用于任何基本数据类型。在const变量的声明之外写入它是一个错误,因此它们必须在声明时进行初始化。举个例子

      const vec3 zAxis = vec3 (0.0, 0.0, 1.0);
      

      结构字段可能不能用const限定。结构变量可以声明为const,并使用结构构造函数初始化。

    3. Constant Expressions

      常量表达式是

      1. 一个文字值(例如,5或true)
      2. 限定为常量的全局或局部变量(即不包括函数参数)
      3. 一种由操作数构成的表达式,这些操作数都是常量表达式,包括获取常量数组的元素或长度、常量结构的字段或常量向量的组成部分
      4. 参数都是常量表达式的构造函数
      5. 一个内置的函数调用,它的参数都是常量表达式,除了纹理查找函数、噪声函数和ftransform。内置函数dFdx、dFdy和fwidth在带有常量表达式参数的初始化器中计算时必须返回0。

      对用户定义函数(非内置函数)的函数调用不能用于形成常量表达式。

      积分常数表达式是计算结果为标量整数的常数表达式。

      当相同的常量表达式出现在多个着色器中时,常量表达式将以不变的方式计算,以便在多个着色器中创建相同的值。

    4. Attribute

      attribute 限定符用于声明从OpenGL以每个顶点为基础传递给顶点着色器的变量。除了顶点着色器之外,在任何类型的着色器中声明属性变量都是错误的。就顶点着色器而言,属性变量是只读的。属性变量的值通过OpenGL顶点API传递给顶点着色器,或者作为顶点数组的一部分。它们将顶点属性传递给顶点着色器,并期望在每次运行顶点着色器时改变。只能与浮点数、浮点向量和矩阵一起使用。属性变量不能声明为数组或结构。

      举几个例子

      attribute vec4 position;
      attribute vec3 normal;
      attribute vec2 texCoord;
      

      所有标准的OpenGL顶点属性都有内置的变量名,以方便用户程序和OpenGL顶点函数之间的集成。

      预计图形硬件将有少量的固定位置来传递顶点属性。因此,OpenGL着色语言将每个非矩阵属性变量定义为拥有最多四个浮点值的空间。vec4)。可以使用的属性变量的数量有一个实现限制,如果超过了这个限制,就会导致链接错误。(未使用的声明属性变量不排除这个限制。) float属性对这个限制的计数与vec4相同,因此应用程序可能需要考虑将4个不相关的浮动属性组打包到一个vec4中,以便更好地利用底层硬件的功能。一个矩阵属性会使用多个属性位置。所使用的位置数将等于矩阵中的列数。

      属性变量必须具有全局作用域,并且必须在第一次使用之前在函数体之外声明。

    5. Uniform

      uniform限定符用于声明全局变量,这些全局变量的值在被处理的整个原语中都是相同的。所有uniform变量都是只读的,并且在链接时或通过API外部初始化。链接时间的初始值要么是变量初始化器的值(如果存在),要么是0(如果没有初始化器)。采样器类型不能有初始化器。

      举个例子:

      uniform vec4 lightPosition;
      uniform vec3 color = vec3(0.7, 0.7, 0.2); // value assigned at link time
      

      uniform限定符可以用于任何基本数据类型,也可以用于声明其类型为结构的变量或任意结构的数组。

      对于可以用于每种类型着色器的uniform的存储量,有一个实现依赖的限制,如果超过了这个限制,就会导致编译时或链接时错误。声明但未使用的uniform变量不排除这一限制。用户定义的uniform变量的数量和内建的uniform变量的数量被使用在一个着色器被加在一起,以确定是否已经超过了可用的uniform存储。

      如果多个着色器链接在一起,那么它们将共享一个单一的全局统一名称空间。因此,具有相同名称的统一变量的类型和初始化器必须在链接到单个可执行文件的所有着色器之间匹配。有些着色器为特定的统一变量提供初始化器是合法的,而另一些着色器则不合法,但是所有提供的初始化器必须是相等的。

    6. Varying

      varying的变量提供了顶点着色器、片段着色器和它们之间固定的功能之间的接口。顶点着色器将计算每个顶点的值(例如颜色,纹理坐标,等等),并将以varying写入变量声明。顶点着色器也可以读取varying变量,得到相同的值。如果在写入顶点着色器之前读取了一个varying变量,那么它将返回未定义的值。

      根据定义,每个顶点设置不同的变量,并以透视正确的方式在被渲染的原语上插入。如果是单采样,则将该值插值到像素的中心,如果存在centroid限定词,则忽略该值。如果多次采样和变化不符合centroid的条件,则必须将该值插值到像素的中心,或像素内的任何位置,或像素的一个样本。如果多采样和 varying符合centroid的条件,则必须将值插值到位于被渲染的像素和原语中的一个点,或者插值到位于原语中的一个像素样本。由于centroid的位置不太规则,它们的导数可能不如非质心变变量准确。

      当使用centroid关键字时,它必须立即在varying关键字之前。

      一个片段着色器可以从不同的变量和读取的值将是内插的值,作为片段的位置在原语的一个函数。一个片段着色器不能写入一个变化的变量。

      在链接顶点和片段着色器中声明的具有相同名称的可变变量的centroidinvariant限定符的类型和存在必须匹配,否则链接命令将失败。只有那些在片段着色器可执行文件中使用的变量(即读取)必须由顶点着色器可执行文件写入;允许在顶点着色器中声明多余的可变变量。

      可变变量的声明如下面的示例所示

      varying vec3 normal;
      centroid varying vec2 TexCoord;
      invariant centroid varying vec4 Color;
      

      varying限定符只能用于浮点数、浮点向量、矩阵或它们的数组。结构不能改变。

      如果没有顶点着色器可执行文件是活动的,那么OpenGL的固定功能管道将计算由片段着色器可执行文件消耗的内置可变变量的值。类似地,如果没有片段着色可执行文件是活动的,顶点着色可执行文件负责计算和写入OpenGL的固定功能片段管道所需的变化变量。

      不同的变量需要具有全局作用域,并且必须在函数体之外声明,然后才使用它们。

  4. 参数限定符

    参数可以有这些限定符。

    QualifierMeaning
    < none: default >对于传递给函数的函数参数
    in对于传递给函数的函数参数
    out对于从函数中返回但在传入时未初始化以供使用的函数参数
    inout对于同时传入和传出函数的函数参数
  5. 方差和不变限定符

    方差是指在不同程序中从同一个表达式得到不同值的可能性。例如,说两个顶点着色器,在不同的程序,每个设置gl_Position在两个着色器相同的表达式,和输入值到那个表达式是相同的,当两个着色器运行。这是可能的,由于两个着色器的独立编译,分配给gl_Position的值不完全相同时,两个着色器运行。

    一般来说,这样的差异是允许的着色器。当某一特定输出变量不存在这样的方差时,该变量称为不变变量。

    1. 不变的限定符

      为了确保特定的输出变量是不变的,有必要使用invariant限定符。它可以用于将先前声明的变量限定为不变变量

      invariant gl_Position; // make existing gl_Position be invariant
      varying vec3 Color;
      invariant Color; // make existing Color be invariant
      

      或在声明变量时作为声明的一部分

      invariant varying vec3 Color;
      

      当与声明结合使用时,不变限定符必须出现在任何存储限定符(可变的)之前。只有顶点着色器输出的变量可以作为不变性的候选变量。这包括用户定义的变化变量、内置的顶点端变化变量以及特殊的顶点变量gl_Position和gl_PointSize。对于不同的变量离开一个顶点着色器和进入一个片段着色器与相同的名字,invariant关键字必须在顶点和片段着色器中使用。invariant关键字后面可以跟一个逗号分隔的前面声明的标识符列表。所有不变式的使用必须在全局范围内,并且在使用声明为invariant的变量之前。

      为了保证一个特定的输出变量在两个程序中不变性,下面也必须为真:

      1. 输出变量在两个程序中都被声明为不变变量。
      2. 表达式和流控制消耗的所有着色器输入变量都必须输入相同的值,从而为输出变量赋值。
      3. 纹理格式、texel值和纹理过滤的设置对于任何影响输出变量值的纹理函数调用都是相同的。
      4. 所有输入值都以相同的方式进行操作。消费表达式和任何中间表达式中的所有操作必须是相同的,具有相同的操作数顺序和相同的结合性,从而给出相同的求值顺序。中间变量和函数必须声明为相同的类型,使用相同的显式或隐式精度限定符。影响输出值的任何控制流都必须是相同的,用于确定该控制流的任何表达式也必须遵循这些不变性规则。
      5. 所有导致设置不变输出变量的数据流和控制流都驻留在单个编译单元中。

      本质上,导致不变输出的所有数据流和控制流必须匹配。

      在最初,默认情况下,所有输出变量都允许是可变的。要强制所有输出变量保持不变,

      #pragma STDGL invariant(all)
      

      如果在声明任何变量或函数之后使用此pragma,则行为不变的输出集是未定义的。在fragment shader中使用这个pragma是一个错误。

      通常,不变性是以优化灵活性为代价来保证的,因此使用不变性可以降低性能。因此,使用这个pragma是为了帮助调试,以避免单独声明所有输出变量为不变变量。

    2. 常数表达式的不变性

      必须保证常量表达式的不变性。如果一个特定的常量表达式再次出现在相同的着色器或不同的着色器中,它的计算结果必须相同。这包括在一个顶点和片段着色器中出现的相同表达式,或者在不同的顶点或片段着色器中出现的相同表达式。

      当对不变变量进行操作时,常量表达式的计算结果必须与前面描述的相同。

  6. 资格顺序

    当有多种资格时,他们必须遵循严格的顺序。这个顺序如下

    invariant-qualifier storage-qualifier 
    storage-qualifier parameter-qualifier
    

表达式

  1. 操作符

    OpenGL底纹语言有以下操作符。标有保留的是非法的。

    优先级算符分类操作符结合律
    1(highest)放在括号里的分组( )NA
    2数组下标函数调用和构造函数结构字段或方法选择器t修复递增和递减[ ] ( ) .++ –左到右
    3前缀增量和递减一元(波浪号保留)++ – + - ~ !右到左
    4乘法(模量保留)*** / %**左到右
    5递增法+ -左到右
    6位操作转变(保留)<< >>左到右
    7相关的< > <= >=左到右
    8平等== !=左到右
    9位操作和(保留)&左到右
    10位专有或(保留)^左到右
    11bit-wise包含或(保留)|左到右
    12逻辑与&&左到右
    13逻辑异或^^左到右
    14逻辑包容或||左到右
    15选择? :右到左
    16算术赋值(保留模、移位和按位赋值)=+= -= *= /= %= <<= >>= &= ^=右到左
    17(lowest)顺序左到右

    没有地址操作符,也没有取消引用操作符。没有typecast操作符,而是使用构造函数。

  2. 函数调用

    如果函数返回一个值,那么可以将对该函数的调用用作表达式,其类型将是用于声明或定义函数的类型。

  3. 构造函数

    构造函数使用函数调用语法,其中函数名是一种类型,调用生成该类型的对象。构造函数在初始化项和表达式中使用的方式是相同的。参数用于初始化构造的值。构造函数可用于请求数据类型转换,以从一个标量类型更改为另一个标量类型,或从较小的类型构建较大的类型,或将较大的类型减少为较小的类型。

    通常,构造函数不是具有预先确定原型的内置函数。对于数组和结构,每个元素或字段的构造函数中必须有一个参数。对于其他类型,参数必须提供足够数量的组件来执行初始化,如果包含太多的参数而不能全部使用,则是错误的。细则。下面实际列出的原型只是示例的子集。

    1. 转换和标量构造函数

      标量类型之间的转换如下原型所示:

      int(bool) // converts a Boolean value to an int
      int(float) // converts a float value to an int
      float(bool) // converts a Boolean value to a float
      float(int) // converts an integer value to a float
      bool(float) // converts a float value to a Boolean
      bool(int) // converts an integer value to a Boolean
      

      当使用构造函数将浮点数转换为整型数时,将删除浮点数的小数部分。

      当使用构造函数将int或float转换为bool时,0和0.0被转换为false,非零值被转换为true。当构造函数用于将bool转换为int或float时,false被转换为0或0.0,true被转换为1或1.0。

      恒等构造函数,比如float(float)也是合法的,但用处不大.

      带有非标量参数的标量构造函数可用于从非标量中获取第一个元素。例如,构造函数float(vec3)将选择vec3参数的第一个组件。

    2. 向量和矩阵构造函数

      构造函数可用于从一组标量、向量或矩阵创建向量或矩阵。这包括缩短向量的能力。

      如果向量构造函数有单个标量参数,则使用它将构造的向量的所有组件初始化为该标量的值。如果一个矩阵构造函数有一个标量参数,那么它将用于初始化矩阵对角线上的所有组件,其余组件将被初始化为0.0。

      如果一个向量是由多个标量、向量或矩阵,或这些向量的混合构造的,那么向量的组成部分将按照参数的组成部分的顺序构造。这些参数将从左到右依次使用,每个参数在使用下一个参数中的任何组件之前,按顺序使用它的所有组件。类似地,用多个标量或向量,或它们的混合来构造矩阵。矩阵组件将按照列主顺序构造和使用。在这些情况下,参数中必须提供足够的组件,以便为构造的值中的每个组件提供初始化器。在最后使用的参数之外提供额外参数是错误的。

      如果一个矩阵是由一个矩阵构造的,那么结果中的每个组件(第i列,第j行)在参数中都有相应的组件(第i列,第j行)将从那里初始化。所有其他组件将被初始化为单位矩阵。如果一个矩阵参数给了一个矩阵构造函数,那么拥有任何其他参数都是错误的。

      如果构造函数的参数的基本类型(bool、int或float)与正在构造的对象的基本类型不匹配,则使用(上面的)标量构造规则转换参数.

      下面是一些有用的向量构造函数

      vec3(float) // initializes each component of with the float
      vec4(ivec4) // makes a vec4 with component-wise conversion
      vec2(float, float) // initializes a vec2 with 2 floats
      ivec3(int, int, int) // initializes an ivec3 with 3 ints
      bvec4(int, int, float, float) // uses 4 Boolean conversions
      vec2(vec3) // drops the third component of a vec3
      vec3(vec4) // drops the fourth component of a vec4
      vec3(vec2, float) // vec3.x = vec2.x, vec3.y = vec2.y, vec3.z = float
      vec3(float, vec2) // vec3.x = float, vec3.y = vec2.x, vec3.z = vec2.y
      vec4(vec3, float)
      vec4(float, vec3)
      vec4(vec2, vec2)
          
      
      vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
      vec4 rgba = vec4(1.0); // sets each component to 1.0
      vec3 rgb = vec3(color); // drop the 4th component  
      
      
      //初始化一个矩阵的对角线,将所有其他元素设为零:
      mat2(float)
      mat3(float)
      mat4(float)
          
      // 为了通过指定向量或标量来初始化一个矩阵,组件被按照主列顺序分配给矩阵元素。
      mat2(vec2, vec2); // one column per argument
      mat3(vec3, vec3, vec3); // one column per argument
      mat4(vec4, vec4, vec4, vec4); // one column per argument
      mat3x2(vec2, vec2, vec2); // one column per argument
      mat2(float, float, // first column
      float, float); // second column
      mat3(float, float, float, // first column
      float, float, float, // second column
      float, float, float); // third column
      mat4(float, float, float, float, // first column
      float, float, float, float, // second column
      float, float, float, float, // third column
      float, float, float, float); // fourth column
      mat2x3(vec2, float, // first column
      vec2, float); // second column 
      
      //还有很多其他的可能性,可以用向量和标量构造矩阵,只要有足够的分量来初始化矩阵。从一个矩阵构造一个矩阵:
      mat3x3(mat4x4); // takes the upper-left 3x3 of the mat4x4
      mat2x3(mat4x2); // takes the upper-left 2x2 of the mat4x4, last row is 0,0
      mat4x4(mat3x3); // puts the mat3x3 in the upper-left, sets the lower right
       // component to 1, and the rest to 0
      
    3. 结构体构造函数

      一旦定义了结构体,并且为其类型提供了名称,就可以使用具有相同名称的构造函数来构造该结构的实例。

      例如:

      struct light {
       float intensity;
       vec3 position;
      };
      light lightVar = light(3.0, vec3(1.0, 2.0, 3.0));
      

      构造函数的参数将用于按顺序设置结构的字段,每个字段使用一个参数。每个参数必须与它所设置的字段的类型相同。

      结构构造函数可以用作初始值设定项或在表达式中使用。

    4. 数组构造函数

      数组类型也可以用作构造函数名,然后可以在表达式或初始化器中使用构造函数名。例如,

      const float c[3] = float[3](5.0, 7.2, 1.1);
      const float d[3] = float[](5.0, 7.2, 1.1);
      float g;
      ...
      float a[5] = float[5](g, 1, g, 2.3, g);
      float b[3];
      b = float[3](g, g + 1.0, g + 2.0);
      

      参数的数量必须与所构造数组的大小完全相同。如果构造函数中没有显示大小,那么数组的大小将显式调整为提供的参数的数量。参数从元素0开始按顺序分配给所构造数组的元素。每个参数必须与数组的元素类型相同.

    5. 向量分量

      向量各分量的名称用单个字母表示。为了便于标记,根据位置、颜色或纹理坐标向量的常用用法,将几个字母与每个组件关联起来。可以通过在变量名后面加上句点()来选择向量的各个分量。然后是组件名

      支持的组件名称是:

      {x, y, z, w}在访问表示点或法线的向量时很有用
      {r, g, b, a}在访问表示颜色的向量时非常有用
      {s, t, p, q}当访问代表纹理坐标的向量时非常有用

      例如,组件名称x、r和s是向量中相同(第一个)组件的同义词。

      请注意纹理的第三个组成部分,也就是OpenGL中的r,已经被重新命名为p,以避免与颜色r(表示红色)混淆。

      访问超出vector类型声明的组件是错误的,因此,例如:

      vec2 pos;
      pos.x // is legal
      pos.z // is illegal
      

      组件选择语法允许多个组件通过在句点(.)后面附加它们的名称(来自相同的名称集)来进行选择。

      vec4 v4;
      v4.rgba; // is a vec4 and the same as just using v4,
      v4.rgb; // is a vec3,
      v4.b; // is a float,
      v4.xy; // is a vec2,
      v4.xgba; // is illegal - the component names do not come from 
       // the same set.
      

      组件的顺序可以是不同的,以混匀它们,或复制:

      vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
      vec4 swiz= pos.wzyx; // swiz = (4.0, 3.0, 2.0, 1.0)
      vec4 dup = pos.xxyy; // dup = (1.0, 1.0, 2.0, 2.0)
      

      这种表示法比构造函数语法更简洁。要形成一个r值,它可以应用于任何表达式,从而得到一个向量r值。

      组件组符号可以出现在表达式的左边。

      vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
      pos.xw = vec2(5.0, 6.0); // pos = (5.0, 2.0, 3.0, 6.0)
      pos.wx = vec2(7.0, 8.0); // pos = (8.0, 2.0, 3.0, 7.0)
      pos.xx = vec2(3.0, 4.0); // illegal - 'x' used twice
      pos.xy = vec3(1.0, 2.0, 3.0); // illegal - mismatch between vec2 and vec3
      

      要形成l-值,必须将swizzling应用于向量类型的l-值,不包含重复组件,并根据指定组件的数量产生标量或向量类型的l-值。

      数组下标语法也可以应用于向量,以提供数值索引。在vec4 pos中;[2]为pos的第三个元素,与pos.z等价。这允许在向量中建立变量索引,也允许以通用的方式访问组件。任何整数表达式都可以用作下标。第一个分量在指标0处。使用带有负值或大于或等于向量大小的常数积分表达式从向量读取或写入向量是非法的。当使用非常量表达式进行索引时,如果索引为负,或大于或等于向量的大小,则行为是未定义的。

    6. 基质主要成分

      矩阵的组件可以使用数组下标语法访问。对一个矩阵应用一个下标将该矩阵视为一个列向量数组,并选择一个列,其类型为与该矩阵大小相同的向量。最左边的列是列0。第二个下标将作用于列向量,正如前面定义的向量。因此,两个下标分别选择列和行。

      mat4 m;
      m[1] = vec4(2.0); // sets the second column to all 2.0
      m[0][0] = 1.0; // sets the upper left element to 1.0
      m[2][3] = 2.0; // sets the 4th element of the third column to 2.0
      

      当访问具有非常量表达式的矩阵边界之外的组件时,行为是未定义的。访问一个具有常数表达式的矩阵是错误的,它超出了该矩阵的边界。

    7. 结构和数组操作

      结构的字段和数组的长度方法是使用period(.)来选择的。

      总的来说,只有以下操作符可以作为整体实体对数组和结构进行操作

      field or method selector.
      equality== !=
      assignment=
      indexing (arrays only)[ ]

      只有当两个操作数的大小和类型相同时,才允许使用相等运算符和赋值运算符。结构类型必须具有相同的声明结构。两个数组操作数的大小都必须显式确定。当使用相等运算符时,当且仅当所有字段在组件方面相等时,两个结构相等;当且仅当所有元素在组件方面相等时,两个数组相等.

      使用数组下标操作符([])访问数组元素。访问数组元素的一个例子是

      diffuseColor += lightIntensity[3] * NdotL;
      

      数组下标从0开始。使用类型为整数的表达式访问数组元素。

      行为是未定义的,如果一个着色器下标一个数组的索引小于0或大于或等于数组声明的大小。

      数组也可以通过方法操作符(。)访问。和length方法查询数组的大小:

    8. 分配

      值对变量名的赋值是通过赋值操作符(=)完成的,比如lvalue = expression, 赋值运算符将表达式的值存储到左值中。表达式和左值必须具有相同的类型,或者表中具有将转换为左值类型的类型,在这种情况下,将在执行赋值之前对表达式进行隐式转换。任何其他需要的类型转换都必须通过构造函数显式地指定。l值必须是可写的。内置类型、整个结构或数组、结构字段、带有字段选择器的l-value变量。应用于选择没有重复字段的组件或混用项时,括号内的l-值和使用数组下标操作符([])解引用的l-值都是l-值。 其他二进制或一元表达式、函数名、重复字段的混和和常量不能是l值。三元运算符(?:)也不允许作为l值。

      赋值左侧的表达式在赋值右侧的表达式之前计算。其他赋值运算符有

      1. 算术赋值是(+=)的加法,(-=)的减法,(*=)的乘法,(/=)的除法。
      2. 赋值模数为(%=),左移为(<<=),右移为(>>=),包含或进入(|=),排他或进入(^=)保留以备将来使用。

      在写入(或初始化)变量之前读取它是合法的,但是值是未定义的。

    9. 表达式

      着色器语言中的表达式是由以下内容构建的:

      1. bool、int、float、所有向量类型和所有矩阵类型的常量

      2. 所有类型的构造函数

      3. 所有类型的变量名

      4. 应用了length方法的数组名称

      5. 下标数组的名字

      6. 函数调用返回值

      7. 组件字段选择器和数组下标结果

      8. 带括号的表达式。任何表达式都可以加上括号。括号可用于对操作进行分组。括号内的操作在跨括号的操作之前完成

      9. 算术二进制运算符的加法(+)、减法(-)、乘法(*)和除法(/)对整数和浮点标量、向量和矩阵进行操作。在操作数类型转换之后,所有算术二进制操作符都会产生与它们操作的操作数相同的基本类型(整型或浮点型)。转换后,以下情况是有效的:

        1. 这两个操作数是标量。在这种情况下,将应用操作,生成一个标量
        2. 一个操作数是标量,另一个是向量或矩阵。在这种情况下,标量操作独立地应用于向量或矩阵的每个分量,从而得到相同大小的向量或矩阵
        3. 这两个操作数是相同大小的向量。在本例中,操作是按组件方式完成的,结果得到相同的大小向量
        4. 操作符是加法(+)、减法(-)或除法(/),操作数是具有相同行数和相同列数的矩阵。在这种情况下,操作是在分量上完成的,结果是相同大小的矩阵
        5. 运算符是乘法(),其中两个操作数是矩阵,或者一个操作数是向量,另一个是矩阵。右向量操作数被视为列向量,左向量操作数被视为行向量。在所有这些情况下,都要求左操作数的列数等于右操作数的行数。然后,乘法()操作进行线性代数乘法,生成一个与左操作数相同的行数和与右操作数相同的列数的对象。

        除以0不会导致异常,但会导致未指定的值。使用内置函数点积、叉乘、矩阵积和外积,分别得到向量点积、向量叉乘、矩阵分量的乘法,以及列向量与行向量的矩阵积。

      10. 运算模(%)被保留下来以备将来使用

      11. 算术一元运算符对整数或浮点值(包括向量和矩阵)进行反求(-)、后增和前减(-和++)操作。所有一元运算符都按组件方式对其操作数进行操作。这些结果与他们操作的类型相同。对于后递增和前递减,表达式必须是可赋值的表达式(l值)。预递增和预递减对其操作的表达式内容加或减1或1.0,预递增或预递减表达式的值就是该修改的结果值。后递增和后递减表达式在其操作的表达式内容上加或减1或1.0,但结果表达式在执行后递增或后递减之前具有表达式的值。

      12. 大于(>)、小于(<)、大于或等于(>=)和小于或等于(<=)的关系运算符只对标量整数和标量浮点表达式进行操作。结果是标量布尔值。要么操作数的类型必须匹配,要么将“隐式转换”中的转换应用于整数操作数,在此之后类型必须匹配。要对向量进行组件式的关系比较,请使用内置函数lessThan、lessThan equal、greaterThan和greaterThanEqual。

      13. 相等操作符equal(==)和not equal(!=)对所有类型进行操作。它们产生一个标量布尔值。如果操作数类型不匹配,则必须将“隐式转换”应用于一个操作数,从而使它们匹配,在这种情况下完成此转换。对于向量、矩阵、结构和数组,一个操作数的所有组件、字段或元素必须与另一个操作数中的对应组件、字段或元素相等,才能认为操作数相等。要获得向量的组件式相等结果,请使用内置函数equal和notEqual。

      14. 逻辑二进制操作符和(&&)、or(| |)和exclusive or(^^)仅对两个布尔表达式进行操作,并生成一个布尔表达式。And(&&)只有在左手操作数为真时才会对右手操作数求值。Or(| |)只会在左手操作数为false时对右手操作数求值。Exclusive or(^^)将始终对两个操作数求值。

      15. 逻辑一元运算符not(!)。它仅对布尔表达式进行操作,并生成一个布尔表达式。要对一个向量进行操作,请使用内置函数not。

      16. sequence(,)操作符,通过在以逗号分隔的表达式列表中返回最右表达式的类型和值来对表达式进行操作。所有表达式都按从左到右的顺序求值。

      17. 三元选择操作符(?😃。它对三个表达式(exp1 ?exp2: exp3)。该运算符计算第一个表达式,该表达式必须生成一个标量布尔值。如果结果为真,则选择对第二个表达式求值,否则选择对第三个表达式求值。只计算第二个和第三个表达式中的一个。第二个和第三个表达式可以是任何类型,只要它们的类型匹配,可以应用于其中一个表达式以使它们的类型匹配。所得到的匹配类型就是整个表达式的类型。

      18. 和(&)或(|),异或(^),而不是(~),右移(> >),左移(< <)。这些操作符是为将来使用而保留的。

    10. 向量与矩阵运算

      除了少数例外,操作都是组件式的。通常,当一个算子作用于一个向量或矩阵时,它是独立地作用于这个向量或矩阵的每个分量,以一种分量明智的方式。例如,

      vec3 v, u;
      float f;
      v = u + f;
      // ----------------将等于
      v.x = u.x + f;
      v.y = u.y + f;
      v.z = u.z + f;
      // -----------------或者
      vec3 v, u, w;
      w = v + u;
      // ------------------也可以
      w.x = v.x + u.x;
      w.y = v.y + u.y;
      w.z = v.z + u.z;
      

      同样的,对于大多数运算符和所有整数和浮点向量和矩阵类型。例外是矩阵乘以向量,向量乘以矩阵,矩阵乘以矩阵。它们并不按组件操作,而是执行正确的线性代数乘法

      vec3 v, u;
      mat3 m;
      u = v * m;
      // ------------------等于
      u.x = dot(v, m[0]); // m[0] is the left column of m
      u.y = dot(v, m[1]); // dot(a,b) is the inner (dot) product of a and b
      u.z = dot(v, m[2]);
      // ------------------或者
      u = m * v;
      // ------------------或者
      u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z;
      u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z;
      u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z;
      // ------------------也可以
      mat3 m, n, r;
      r = m * n;
      //-------------------或者
      r[0].x = m[0].x * n[0].x + m[1].x * n[0].y + m[2].x * n[0].z;
      r[1].x = m[0].x * n[1].x + m[1].x * n[1].y + m[2].x * n[1].z;
      r[2].x = m[0].x * n[2].x + m[1].x * n[2].y + m[2].x * n[2].z;
      r[0].y = m[0].y * n[0].x + m[1].y * n[0].y + m[2].y * n[0].z;
      r[1].y = m[0].y * n[1].x + m[1].y * n[1].y + m[2].y * n[1].z;
      r[2].y = m[0].y * n[2].x + m[1].y * n[2].y + m[2].y * n[2].z;
      r[0].z = m[0].z * n[0].x + m[1].z * n[0].y + m[2].z * n[0].z;
      r[1].z = m[0].z * n[1].x + m[1].z * n[1].y + m[2].z * n[1].z;
      r[2].z = m[0].z * n[2].x + m[1].z * n[2].y + m[2].z * n[2].z;
      
  4. 语句和结构

    OpenGL着色器语言的基本构建块是:

    1. 声明和描述
    2. 函数定义
    3. 选择(if - else)
    4. 迭代(for、while和do-while)
    5. 跳转(discardreturnbreakcontinue)

    着色器的整体结构如下所示

    translation-unit:

    global-declaration

    translation-unit global-declaration

    global-declaration:

    function-definition

    declaration

    也就是说,着色器是声明和函数体的序列。函数体定义为

    function-definition:

    function-prototype { statement-list }

    statement-list:

    statement

    statement-list statement

    statement:

    compound-statement

    simple-statement

    花括号用于将语句序列分组为复合语句。

    compound-statement:

    { statement-list }

    simple-statement:

    declaration-statement

    expression-statement

    selection-statement

    iteration-statement

    jump-statement

    简单的声明、表达式和跳转语句以分号结束。

    1. 函数定义

      如上面的语法所示,一个有效的着色器是一个全局声明和函数定义的序列。函数的声明如下例所示:

      // prototype
      returnType functionName (type0 arg0, type1 arg1, ..., typen argn);
      

      函数的定义是

      // definition
      returnType functionName (type0 arg0, type1 arg1, ..., typen argn)
      {
       	// do some computation
       	return returnValue;
      }
      

      其中返回类型必须存在并包含类型。每个类型都必须包含一个类型,并且可以选择包含一个参数限定符和/或常量。

      调用函数时,函数名后面跟着括号中的参数列表。

      数组允许作为参数和返回类型。在这两种情况下,必须显式地调整数组的大小。
      传递或返回数组时只使用它的名称,不使用括号,数组的大小必须与函数声明中指定的大小匹配。

      结构也允许作为参数。返回类型也可以是structure。

      所有函数在被调用之前都必须用原型声明或用主体定义。例如:

      float myfunc (float f, // f is an input parameter
       		out float g); // g is an output parameter
      

      没有返回值的函数必须声明为void。不接受输入参数的函数不需要在参数列表中使用void,因为需要原型(或定义),因此当声明一个空参数列表“()”时不会产生歧义。为了方便起见,提供了作为参数列表的习惯用法“(void)”。

      可以重载函数名。这允许将相同的函数名用于多个函数,只要参数列表类型不同。如果函数的名称和参数类型匹配,那么它们的返回类型和参数限定符也必须匹配。在检查类型是否匹配时不包含限定符,函数签名匹配仅基于参数类型。内置函数中大量使用了重载。在解析函数调用时,将寻找函数的形参类型(签名)的精确类型匹配。这还包括数组大小的精确匹配。如果找到了精确匹配,则忽略其他签名,并使用精确匹配。否则,如果没有找到精确的匹配,那么将隐式转换应用于调用参数,前提是这样可以使它们的类型匹配签名。在这种情况下,如果有多种方法将这些转换应用于调用的实际参数,从而使调用能够匹配多个签名,那么这是一个语义错误。

      例如,内置的点积函数有以下原型:

      float dot (float x, float y);
      float dot (vec2 x, vec2 y);
      float dot (vec3 x, vec3 y);
      float dot (vec4 x, vec4 y);
      

      用户定义函数可以有多个声明,但只能有一个定义。着色器可以重新定义内置函数。如果一个内置函数在调用它之前在着色器中被重新声明(即原型是可见的),那么链接器将只尝试在与它链接的设置着色器中解析该调用。

      main函数被用作可执行着色器的入口点。一个着色器不需要包含一个名为main的函数,但是一个着色器在一组着色器中链接在一起形成一个单一的着色器可执行文件必须。这个函数不接受参数,也不返回值,并且必须声明为void类型.

      使用任何其他签名或返回类型声明或定义函数main是错误的。

      1. 函数调用约定

        函数是通过值返回来调用的。这意味着在调用时将输入参数复制到函数中,而在函数退出之前将输出参数复制回调用者。由于该函数使用参数的本地副本,所以在函数中不存在变量混叠的问题。在调用时,按从左到右的顺序计算输入参数。但是,没有定义将输出参数复制回调用者的顺序。通过函数定义或声明来控制哪些参数被复制到和/或输出:

        1. 关键字in用作限定符,表示要将参数复制进去,而不是复制出来。
        2. 关键字out用作限定符,表示要将参数复制出去,而不是复制进去。
          应该尽可能使用此方法,以避免不必要地复制参数。
        3. 关键字inout用作限定符,表示将同时复制入和复制出参数。
        4. 如果函数参数声明时没有这样的限定符,其意义与在。

        在函数中,允许写入只允许输入的参数。只有函数的副本被修改。这可以通过使用const限定符声明一个参数来防止。在调用函数时,计算值不为l值的表达式不能传递给声明为out或inout的参数。

        在函数的返回类型上不允许使用限定符。

        但是,const限定符不能与out或inout一起使用。因此,函数定义可以有未命名的参数。

        递归是不允许的,即使是静态的。如果程序的静态函数调用图包含循环,则存在静态递归。

    2. 选择区对象

      着色语言中的条件控制流是通过if或if-else来完成的。

      如果表达式的计算结果为true,则执行true-statement。如果计算结果为false,并且还有其他部分,则执行false语句。
      任何类型计算为布尔值的表达式都可以用作条件表达式bool-表达式。不接受向量类型作为if的表达式。条件可以嵌套

    3. 迭代

      For、while、do循环允许:

      for (init-expression; condition-expression; loop-expression)
       sub-statement
      while (condition-expression)
       sub-statement
      do
       statement
      while (condition-expression)
      

      for循环首先计算初始表达式,然后计算条件表达式。如果条件表达式的计算结果为true,则执行循环体。在主体执行之后,for循环将对循环表达式求值,然后返回来对条件表达式求值,不断重复,直到条件表达式求值为false为止。然后退出循环,跳过它的主体和循环表达式。由循环表达式修改的变量在退出循环后保持它们的值,前提是它们仍然在作用域中。在init-expression或条件-expression中声明的变量只在for循环的子语句结束之前起作用。

      while循环首先计算条件表达式。如果为真,则执行body。然后重复此操作,直到条件表达式的计算结果为false,退出循环并跳过其主体。
      在条件表达式中声明的变量只在while循环的子语句结束之前起作用。

      do-while循环首先执行主体,然后执行条件表达式。重复此操作,直到条件表达式的计算结果为false,然后退出循环。

      件表达式和init表达式都可以声明和初始化变量,但do-while循环除外,它不能在其条件表达式中声明变量。变量的作用域只持续到形成循环体的子语句结束。

      循环可以嵌套。

      允许无终止循环。非常长的或不终止的循环的结果依赖于平台。

    4. 跳转

      这些是跳转:

      jump_statement:
      continue;
      break;
      return; 
      return expression; 
      discard; // in the fragment shader language only
      

      没有“goto”,也没有其他非结构化的控制流。

      continue 跳转只在循环中使用。它跳过它所在的最内部循环主体的其余部分。对于while和do-while循环,此跳转将跳转到循环条件表达式的下一个求值,循环将从该表达式继续执行前面定义的操作。对于For循环,跳转到循环表达式,然后跳转到条件表达式。

      break 跳转也只能在循环中使用。它只是包含break的最内部循环的立即退出。不再执行条件表达式或循环表达式.

      discard 关键字只允许在片段着色器中使用。它可以在片段着色器中使用,以放弃对当前片段的操作。此关键字将导致丢弃片段,并且不会对任何缓冲区进行更新。它通常在条件语句中使用,举个例子

      if (intensity < 0.0)
       discard;
      

      一个片段着色器可以测试一个片段的alpha值,并基于该测试丢弃片段。然而,应该注意的是,覆盖率测试发生在片段着色器运行之后,覆盖率测试可以改变alpha值。

      return 跳转导致当前函数立即退出。如果它有表达式,那么这是函数的返回值。

      main函数可以使用return。这只会导致main退出,就像到达函数末尾时一样。它并不意味着在碎片着色器中使用丢弃。在定义输出之前在main中使用return将具有与在定义输出之前到达main末尾相同的行为。

  5. 内建变数

    1. 顶点着色器的特殊变量

      一些OpenGL操作仍然在顶点处理器和片段处理器之间的固定功能中继续发生。在片段处理器之后,其他OpenGL操作继续在固定的功能中进行。着色器通过使用内置变量与OpenGL的固定功能通信。

      变量gl_Position仅在顶点语言中可用,用于编写同构顶点位置。所有格式良好的顶点着色器执行都必须在这个变量中写入一个值。它可以在着色器执行期间的任何时候被写入。它也可能被一个顶点着色器读回来后被写入。这个值将被原语组装、裁剪、剔除和其他在顶点处理后对原语进行的固定功能操作所使用。*:*如果编译器检测到没有写入gl_Position,或者在写入之前读取,它们可能会生成诊断消息,但并不是所有这些情况都可以检测到。如果顶点着色器可执行且不写入gl_Position,则其值是未定义的。

      变量gl_PointSize仅在顶点语言中可用,用于顶点着色器编写要栅格化的点的大小。它是以像素为单位的。

      变量gl_ClipVertex只在顶点语言中可用,它为顶点着色器提供了一个位置,用于编写用于用户剪切平面的坐标。用户必须确保剪辑顶点和用户裁剪平面在相同的坐标空间中定义。用户剪辑平面只有在线性变换下才能正常工作。在非线性变换下会发生什么是没有定义的。

      这些用于与固定功能通信的内置顶点着色器变量的内部声明为以下类型:

      vec4 gl_Position; // must be written to
      float gl_PointSize; // may be written to
      vec4 gl_ClipVertex; // may be written to
      

      如果没有写入gl_PointSize或gl_ClipVertex,则它们的值未定义。这些变量中的任何一个都可以在着色器写入它们后被读取回来,以检索写入的内容。先读后写会导致未定义的行为。如果它们被多次写入,则后续操作将使用最后写入的值。

      这些内置变量具有全局作用域。

    2. 片段着色器的特殊变量

      片段着色器可执行文件的输出由OpenGL管道后端的固定函数操作处理。片段着色器使用内置变量gl_FragColor、gl_FragData和gl_FragDepth将值输出到OpenGL管道,除非执行了discard关键字。

      这些变量可能被多次写入。如果是,最后分配的值是后续固定函数管道中使用的值。写入这些变量的值可能在写入后被读回。在写入这些变量之前先读取它们会导致未定义的值。一个片段的固定功能计算深度可以通过读取gl_FragCoord.z 来获得,下面描述。

      写入到gl_FragColor指定了后续固定功能管道将使用的片段颜色。如果随后的固定功能消耗了片段颜色,并且片段着色器可执行文件的执行没有向gl_FragColor写入值,那么所消耗的片段颜色是不明确的。

      如果帧缓冲区被配置为一个颜色索引缓冲区,那么当使用一个片段着色器时行为是不明确的。

      写入到gl_FragDepth将为正在处理的片段建立深度值。如果启用了深度缓冲,并且没有着色器写入gl_FragDepth,那么depth的固定函数值将被用作片段的深度值。如果一个着色器静态地给gl_FragDepth赋值,并且通过着色器有一个没有设置gl_FragDepth的执行路径,那么片段的depth值可能没有为执行该路径的着色器定义。也就是说,如果一组链接的片段着色器静态地包含了对gl_FragDepth的写入,那么它将负责始终写入它。

      一个着色器包含一个静态赋值给一个变量x,如果,预处理后,着色器包含一个将写入x的语句,不管运行时控制流是否会导致该语句被执行。

      变量gl_FragData是一个数组。写入到gl_FragData[n]指定了将被data n的后续固定功能管道使用的片段数据。如果随后的固定功能消耗片段数据和一个片段着色可执行执行不写一个值到它,那么片段数据消耗是不明确的。

      如果一个着色器静态地给gl_FragColor赋值,它可能不会给gl_FragData的任何元素赋值。如果一个着色器静态地向gl_FragData的任何元素写入一个值,它可能不会给gl_FragColor赋值。也就是说,着色器可以给gl_FragColor或gl_FragData赋值,但不能同时赋值。连接在一起的多个着色器也必须一致地只写其中一个变量。

      如果着色器执行discard关键字,则片段被丢弃,并且gl_FragDepth、gl_FragColor和gl_FragData的值变得不相关。

      变量gl_FragCoord是从片段着色器中获得的只读变量,它保存了片段的窗口相对坐标x, y, z和1/w值。如果是多采样,这个值可以是像素内的任何位置,或者是一个片段样本。矩心变化不会进一步限制该值在当前原语中。这个值是在顶点处理后插入原语以生成片段的固定功能的结果。z组件是深度值,如果没有着色器包含没有写入到gl_FragDepth,它将被用于片段的深度。如果着色器有条件地计算gl_FragDepth,但想要固定的功能片段深度,这对于不变性是有用的。

      片段着色器可以访问只读内置变量gl_FrontFacing,如果片段属于前置原语,它的值为true。这种方法的一个用途是通过选择由顶点着色器计算的两种颜色中的一种来模拟双面照明。

      可从片段着色器访问的内置变量本质上是如下所示的类型:

      vec4 gl_FragCoord;
      bool gl_FrontFacing;
      vec4 gl_FragColor;
      vec4 gl_FragData[gl_MaxDrawBuffers];
      float gl_FragDepth;
      

      但是,它们的行为不像没有存储限定符的变量;它们的行为如上所述。这些内置变量具有全局作用域。

    3. 顶点着色器内置属性

      下面的属性名内置在OpenGL顶点语言中,可以在顶点着色器中使用,以访问OpenGL声明的属性的当前值。

      //
      // Vertex Attributes, p. 19.
      //
      attribute vec4 gl_Color;
      attribute vec4 gl_SecondaryColor;
      attribute vec3 gl_Normal;
      attribute vec4 gl_Vertex;
      attribute vec4 gl_MultiTexCoord0;
      attribute vec4 gl_MultiTexCoord1;
      attribute vec4 gl_MultiTexCoord2;
      attribute vec4 gl_MultiTexCoord3;
      attribute vec4 gl_MultiTexCoord4;
      attribute vec4 gl_MultiTexCoord5;
      attribute vec4 gl_MultiTexCoord6;
      attribute vec4 gl_MultiTexCoord7;
      attribute float gl_FogCoord;
      
    4. 内置常量

      下面的内置常量被提供给顶点和片段着色器。

      //
      // Implementation dependent constants. The example values below
      // are the minimum values allowed for these maximums.
      //
      const int gl_MaxLights = 8; // GL 1.0
      const int gl_MaxClipPlanes = 6; // GL 1.0
      const int gl_MaxTextureUnits = 2; // GL 1.3
      const int gl_MaxTextureCoords = 2; // ARB_fragment_program
      const int gl_MaxVertexAttribs = 16; // ARB_vertex_shader
      const int gl_MaxVertexUniformComponents = 512; // ARB_vertex_shader
      const int gl_MaxVaryingFloats = 32; // ARB_vertex_shader
      const int gl_MaxVertexTextureImageUnits = 0; // ARB_vertex_shader
      const int gl_MaxCombinedTextureImageUnits = 2; // ARB_vertex_shader
      const int gl_MaxTextureImageUnits = 2; // ARB_fragment_shader
      const int gl_MaxFragmentUniformComponents = 64;// ARB_fragment_shader
      const int gl_MaxDrawBuffers = 1; // proposed ARB_draw_buffers
      
    5. 统一的内置声明

      为了帮助访问OpenGL处理状态,下面的统一变量被内置于OpenGL着色语言中。

      //
      // Matrix state. p. 31, 32, 37, 39, 40.
      //
      uniform mat4 gl_ModelViewMatrix;
      uniform mat4 gl_ProjectionMatrix;
      uniform mat4 gl_ModelViewProjectionMatrix;
      uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
      //
      // Derived matrix state that provides inverse and transposed versions
      // of the matrices above. Poorly conditioned matrices may result
      // in unpredictable values in their inverse forms.
      //
      uniform mat3 gl_NormalMatrix; // transpose of the inverse of the 
       // upper leftmost 3x3 of gl_ModelViewMatrix
      uniform mat4 gl_ModelViewMatrixInverse;
      uniform mat4 gl_ProjectionMatrixInverse;
      uniform mat4 gl_ModelViewProjectionMatrixInverse;
      uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixTranspose;
      uniform mat4 gl_ProjectionMatrixTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixTranspose;
      uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixInverseTranspose;
      uniform mat4 gl_ProjectionMatrixInverseTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
      uniform mat4 gl_TextureMatrixInverseTranspose[gl_MaxTextureCoords];
      //
      // Normal scaling p. 39.
      //
      uniform float gl_NormalScale;
      //
      // Depth range in window coordinates, p. 33
      //
      struct gl_DepthRangeParameters {
       float near; // n
       float far; // f
       float diff; // f - n
      };
      uniform gl_DepthRangeParameters gl_DepthRange;
      //
      // Clip planes p. 42.
      //
      uniform vec4 gl_ClipPlane[gl_MaxClipPlanes];
      //
      // Point Size, p. 66, 67.
      //
      struct gl_PointParameters {
       float size;
       float sizeMin;
       float sizeMax;
       float fadeThresholdSize;
       float distanceConstantAttenuation;
       float distanceLinearAttenuation;
       float distanceQuadraticAttenuation;
      };
      uniform gl_PointParameters gl_Point;
      //
      // Material State p. 50, 55.
      //
      struct gl_MaterialParameters {
       vec4 emission; // Ecm
       vec4 ambient; // Acm
       vec4 diffuse; // Dcm
       vec4 specular; // Scm
       float shininess; // Srm
      };
      uniform gl_MaterialParameters gl_FrontMaterial;
      uniform gl_MaterialParameters gl_BackMaterial;
      //
      // Light State p 50, 53, 55.
      //
      struct gl_LightSourceParameters {
       vec4 ambient; // Acli
       vec4 diffuse; // Dcli
       vec4 specular; // Scli
       vec4 position; // Ppli
       vec4 halfVector; // Derived: Hi
       vec3 spotDirection; // Sdli
       float spotExponent; // Srli
       float spotCutoff; // Crli
       // (range: [0.0,90.0], 180.0)
       float spotCosCutoff; // Derived: cos(Crli)
       // (range: [1.0,0.0],-1.0)
       float constantAttenuation; // K0
       float linearAttenuation; // K1
       float quadraticAttenuation;// K2
      };
      uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];
      struct gl_LightModelParameters {
       vec4 ambient; // Acs
      };
      uniform gl_LightModelParameters gl_LightModel;
      //
      // Derived state from products of light and material.
      //
      struct gl_LightModelProducts {
       vec4 sceneColor; // Derived. Ecm + Acm * Acs
      };
      uniform gl_LightModelProducts gl_FrontLightModelProduct;
      uniform gl_LightModelProducts gl_BackLightModelProduct; 
      struct gl_LightProducts {
       vec4 ambient; // Acm * Acli
       vec4 diffuse; // Dcm * Dcli
       vec4 specular; // Scm * Scli
      };
      uniform gl_LightProducts gl_FrontLightProduct[gl_MaxLights];
      uniform gl_LightProducts gl_BackLightProduct[gl_MaxLights];
      //
      // Texture Environment and Generation, p. 152, p. 40-42.
      //
      uniform vec4 gl_TextureEnvColor[gl_MaxTextureUnits];
      uniform vec4 gl_EyePlaneS[gl_MaxTextureCoords];
      uniform vec4 gl_EyePlaneT[gl_MaxTextureCoords];
      uniform vec4 gl_EyePlaneR[gl_MaxTextureCoords];
      uniform vec4 gl_EyePlaneQ[gl_MaxTextureCoords];
      uniform vec4 gl_ObjectPlaneS[gl_MaxTextureCoords];
      uniform vec4 gl_ObjectPlaneT[gl_MaxTextureCoords];
      uniform vec4 gl_ObjectPlaneR[gl_MaxTextureCoords];
      uniform vec4 gl_ObjectPlaneQ[gl_MaxTextureCoords];
      //
      // Fog p. 161
      //
      struct gl_FogParameters {
       vec4 color;
       float density;
       float start;
       float end;
       float scale; // Derived: 1.0 / (end - start)
      };
      uniform gl_FogParameters gl_Fog;
      
    6. 易变变量

      与用户定义的变化变量不同,内置的变化变量在顶点语言和片段语言之间没有严格的一对一对应关系。提供了两个集合,分别对应于每种语言。它们的关系如下所述。

      以下内置的可变变量可以在顶点着色器中写入。如果在相应的片段着色器或固定管道中的任何功能使用它或从它派生的状态,一个特定的应该被写入。否则,行为是不明确的。

      varying vec4 gl_FrontColor;
      varying vec4 gl_BackColor;
      varying vec4 gl_FrontSecondaryColor;
      varying vec4 gl_BackSecondaryColor;
      varying vec4 gl_TexCoord[]; // at most will be gl_MaxTextureCoords
      varying float gl_FogFragCoord;
      

      对于gl_FogFragCoord,写入的值将用作OpenGL 1.4的“c”值,规范由固定功能管道实现。例如,如果在eye空间的片段的z坐标被期望为“c”,那么这就是顶点着色器可执行程序应该写入到gl_FogFragCoord的内容。

      与所有数组一样,用于下标gl_TexCoord的索引必须要么是一个整型常量表达式,要么这个数组必须由着色器用一个大小重新声明。大小最多可以是gl_MaxTextureCoords。使用接近0的索引可以帮助实现保存不同的资源

      以下不同的变量可从片段着色器中读取。gl_Color和gl_SecondaryColor名称与传递给顶点着色器语言的属性名称相同。但是,没有名称冲突,因为属性只在顶点着色器中可见,下面的属性只在片段着色器中可见。

      varying vec4 gl_Color;
      varying vec4 gl_SecondaryColor;
      varying vec4 gl_TexCoord[]; // at most will be gl_MaxTextureCoords
      varying float gl_FogFragCoord;
      varying vec2 gl_PointCoord;
      

      gl_Color和gl_SecondaryColor中的值将由系统根据可见的面孔自动从gl_FrontColor、gl_BackColor、gl_FrontSecondaryColor和gl_BackSecondaryColor派生。如果固定功能用于顶点处理,那么gl_FogFragCoord要么是眼空间片段的z坐标,要么是雾坐标的插值。gl_TexCoord[]值是从一个顶点着色器中插入的gl_TexCoord[]值,或者任何基于顶点功能的固定管道的纹理坐标。

      片段着色器gl_TexCoord数组的索引在顶点着色器文本中如上所述。

      gl_PointCoord中的值是二维坐标,指示了当点块纹理被启用时,当前片段在点原语中的位置。它们的范围从0.0到1.0。如果当前原语不是一个点,或者没有启用点块纹理,那么从gl_PointCoord读取的值是不明确的。

  6. 内置函数

    OpenGL着色语言定义了一系列用于标量和向量操作的内置便利函数。许多这些内置的函数可以在不止一种类型的着色器中使用,但有些旨在提供直接映射到硬件,因此只能用于特定类型的着色器。

    内置功能主要分为三类:

    1. 它们以方便的方式公开一些必要的硬件功能,比如访问纹理映射。语言中没有办法让这些函数被着色器模拟。
    2. 它们代表了一个简单的操作(clamp, mix,等等),对于用户来说编写起来非常简单,但是它们非常常见并且可能有直接的硬件支持。对于编译器来说,将表达式映射到复杂的汇编指令是一个非常困难的问题。
    3. 它们代表了图形硬件在某一时刻可能会加速的操作。三角函数属于这一类

    许多函数与公共C库中相同的命名函数相似,但是它们支持向量输入和更传统的标量输入。

    应用程序应该被鼓励使用内建函数而不是在他们自己的着色器代码中做等价的计算,因为内建函数被假定是最优的(例如,可能直接在硬件中支持)。

    用户代码可以通过重新声明和定义相同的名称和参数列表,将内置函数替换为自己的函数。由于内建函数比用户内建函数处于更外部的作用域,因此这样做将隐藏与重新声明的函数同名的所有内建函数。

    当下面指定了内置函数时,其中的输入参数(和相应的输出)可以是float、vec2、vec3或vec4,则使用genType作为参数。对于函数的任何特定用法,对于所有参数和返回类型,实际类型必须是相同的。类似的mat,它可以是任何矩阵的基本类型。

    1. 角度和三角函数

      函数参数指定为角度,假设以弧度为单位。在任何情况下,这些函数都不会产生除零误差。如果一个比率的除数为0,那么结果将是无定义的。

      这些都是组件式操作的。每个组件都有描述。

      语法含义
      genType radians (genType degrees)将角度转换为弧度( π 180 ∗ d e g r e e s \frac{π}{180} \quad * degrees 180πdegrees
      genType degrees (genType radians)将弧度转换成角度( 180 π ∗ r a d i a n s \frac{180}{π} \quad * radians π180radians)
      genType sin (genType angle)标准三角正弦函数
      genType cos (genType angle)标准的三角余弦函数
      genType tan (genType angle)标准三角正切
      genType asin (genType x)反正弦,值在$ \begin{bmatrix} -\frac{π}{2} & \frac{π}{2} \end{bmatrix} $
      genType acos (genType x)反余弦,值在$ \begin{bmatrix} 0 &π \end{bmatrix} $
      genType atan (genType y, genType x)反正切,返回一个正切为y/x的角。用x和y的符号来确定角在哪个象限。值在$ \begin{bmatrix} -π &π \end{bmatrix} $
      genType atan (genType y_over_x)弧相切。返回一个正切为y_over_x的角度。值在$ \begin{bmatrix} -\frac{π}{2} & \frac{π}{2} \end{bmatrix} $
    2. 指数函数

      这些都是组件式操作的。每个组件都有描述

      语法含义
      genType pow (genType x, genType y)返回x的y次方 即$ \begin{matrix} x^y \end{matrix}$
      genType exp (genType x)返回x的自然取幂,即$ \begin{matrix} e^x \end{matrix}$
      genType log (genType x)返回x的自然对数,即$ \begin{matrix} x= e^y \end{matrix}$返回y
      genType exp2 (genType x)返回2的x次方,即$ \begin{matrix} 2^x \end{matrix}$
      genType log2 (genType x)返回以2为底的对x的对数,$ \begin{matrix} x=2^y \end{matrix}$返回y
      genType sqrt (genType x) x \sqrt{x} x
      genType inversesqrt (genType x)$\frac{1}{\sqrt{x}} \quad $
    3. 常用函数

      这些都是组件式操作的。每个组件都有描述。

      语法含义
      genType abs (genType x)如果x >= 0返回x,否则返回-x
      genType sign (genType x)如果x > 0返回1.0,如果x = 0返回0.0,如果x < 0返回-1.0
      genType floor (genType x)返回一个值,该值等于小于或等于x的最近整数
      genType ceil (genType x)返回一个值,该值等于大于或等于x的最近整数
      genType fract (genType x)返回 xfloor (x)
      genType mod (genType x, float y)系数,返回xyfloor (x/y)
      genType mod (genType x, genType y)系数,返回xyfloor (x/y)
      genType min (genType x, genType y)如果x < y返回y,否则返回x。
      genType min (genType x, float y)如果x < y返回y,否则返回x。
      genType clamp (genType x, genType minVal,genType maxVal)返回min (max (x, minVal), maxVal)
      genType mix (genType x, genType y,genType a)返回x和y的线性混合,即x* (1-a)+y * a
      genType step (float edge, genType x)genType step (float edge, genType x)
      genType smoothstep (float edge0, float edge1,genType x)如果x <= edge0返回0.0,如果x >= edge1返回1.0,当edge0 < x < edge1时执行0和1之间的平滑Hermite插值。这在需要一个平滑过渡的阈值函数的情况下非常有用。
    4. 几何函数

      它们以向量的形式作用于向量,而不是组件。

      语法含义
      float length (genType x)返回向量x的长度
      float distance (genType p0, genType p1)返回p0和p1之间的距离
      float dot (genType x, genType y)返回x和y的点积
      vec3 cross (vec3 x, vec3 y)返回x和y的叉乘
      genType normalize (genType x)返回与x方向相同但长度为1的向量
      vec4 ftransform()仅用于顶点着色器。这个函数将确保传入的顶点值将以一种与OpenGL的固定功能转换产生的结果完全相同的方式进行转换。它将用于计算gl_Position,gl_Position = ftransform(),这个功能应该被使用,例如,当一个应用程序在不同的通道渲染相同的几何图形,一个通道使用固定的功能路径渲染,而另一个通道使用可编程的着色器。
      genType faceforward(genType N, genType I,genType Nref)如果dot(Nref, I) < 0返回N,否则返回-N
      genType reflect (genType I, genType N)对于入射向量I和表面方向N,返回反射方向:I - 2∗点(N, I)∗N N必须已经被标准化,以实现期望的结果
      genType refract(genType I, genType N,对于入射向量I和表面法线N,以及折射率的比值eta,返回折射率向量。射向量I和曲面法线N的输入参数必须已经归一化才能得到预期的结果
    5. 矩阵函数

      语法含义
      mat matrixCompMult (mat x, mat y)将矩阵x乘以矩阵y的分量
      outerProduct(vec2 c, vec2 r)将第一个参数c视为一个列向量(矩阵的一列)和第二个参数r作为一个行向量(矩阵的一行)和一个线性代数矩阵乘以c * r,产生一个矩阵的行数是组件的数量在c和组件的数量列数的r
      transpose(mat2 m)返回m的转置矩阵。输入矩阵m未被修改
    6. 矢量关系函数

      定义(或保留)关系和相等运算符(<、<=、>、>=、==、!=)来生成标量布尔结果。对于向量结果,使用以下内置函数。下面,“bvec”是bvec2、bvec3或bvec4中的一个占位符,“ivec”是ivec2、ivec3或ivec4中的一个占位符,“vec”是vec2、vec3或vec4中的一个占位符。在所有情况下,任何特定调用的输入和返回向量的大小都必须匹配。

      语法含义
      bvec lessThan(vec x, vec y)返回x < y的组件比较
      bvec lessThanEqual(vec x, vec y)返回x <= y的组件比较
      bvec greaterThan(vec x, vec y)返回对x > y的组件比较
      bvec greaterThanEqual(vec x, vec y)返回x >= y的组件比较
      bvec equal(vec x, vec y)返回x == y的组件比较
      bvec notEqual(vec x, vec y)返回x != y的组件比较
      bool any(bvec x)如果x的任意分量为真,则返回真
      bool all(bvec x)只有当x的所有分量都为真时,才返回真
      bvec not(bvec x)返回x的组件逻辑补码
    7. 纹理查找功能

      纹理查找函数对顶点和片段着色器都是可用的。然而,顶点着色器的细节级别不是通过固定的功能来计算的,所以在操作上顶点和片段纹理查找有一些不同。下表中的函数通过采样器提供对纹理的访问,这是通过OpenGL API设置的。纹理属性,如大小、像素格式、维数、过滤方法、mip-map级别的数量、深度比较等等,也由OpenGL API调用定义。通过下面定义的内置函数访问纹理时,会考虑这些属性。

      如果一个非阴影纹理调用了一个代表深度纹理的采样器,并打开了深度比较,那么结果是不明确的。如果一个阴影纹理调用一个代表深度纹理并关闭深度比较的采样器,那么结果是不明确的。如果一个阴影纹理调用一个不代表深度纹理的采样器,那么结果是不明确的。

      在下面的所有函数中,fragment shaders的bias参数是可选的。在顶点着色器中不接受偏差参数。对于一个片段着色器,如果存在偏差,它会在执行纹理访问操作之前被添加到计算的细节级别。如果没有提供bias参数,那么实现会自动选择细节级别:对于没有mip映射的纹理,直接使用纹理。如果它被mip映射并运行在一个片段着色器中,那么由实现计算的LOD将用于执行纹理查找。如果它是mip映射并且运行在顶点着色器上,那么使用基础纹理。

      以“Lod”为后缀的内置属性只允许在顶点着色器中使用。对于“Lod”函数,直接使用Lod作为详细级别。

      vec4 texture1D (sampler1D sampler, float coord [, float bias] ) 
      vec4 texture1DProj (sampler1D sampler, vec2 coord [, float bias] ) 
      vec4 texture1DProj (sampler1D sampler, vec4 coord [, float bias] ) 
      vec4 texture1DLod (sampler1D sampler, float coord, float lod) 
      vec4 texture1DProjLod (sampler1D sampler, vec2 coord, float lod) 
      vec4 texture1DProjLod (sampler1D sampler, vec4 coord, float lod)
      // 使用纹理坐标库对当前绑定到采样器的一维纹理进行纹理查找。
          
      vec4 texture2D (sampler2D sampler, vec2 coord [, float bias] ) 
      vec4 texture2DProj (sampler2D sampler, vec3 coord [, float bias] ) 
      vec4 texture2DProj (sampler2D sampler, vec4 coord [, float bias] )
       vec4 texture2DLod (sampler2D sampler, vec2 coord, float lod)
       vec4 texture2DProjLod (sampler2D sampler, vec3 coord, float lod) 
       vec4 texture2DProjLod (sampler2D sampler, vec4 coord, float lod)
       // 使用纹理坐标库在当前绑定到采样器的2D纹理中进行纹理查找。vec4 coord变体忽略了coord的第三个组件。
          
       vec4 texture3D (sampler3D sampler, vec3 coord [, float bias] ) 
       vec4 texture3DProj (sampler3D sampler, vec4 coord [, float bias] ) 
       vec4 texture3DLod (sampler3D sampler, vec3 coord, float lod) 
       vec4 texture3DProjLod (sampler3D sampler, vec4 coord, float lod)
       // 使用纹理坐标库在当前绑定到采样器的3D纹理中进行纹理查找。
          
       vec4 textureCube (samplerCube sampler, vec3 coord [, float bias] ) 
       vec4 textureCubeLod (samplerCube sampler, vec3 coord, float lod)
       // 使用纹理坐标库在当前绑定到采样器的立方体贴图纹理中进行纹理查找。
          
       vec4 shadow1D (sampler1DShadow sampler, vec3 coord [, float bias] ) 
       vec4 shadow2D (sampler2DShadow sampler, vec3 coord [, float bias] ) 
       vec4 shadow1DProj (sampler1DShadow sampler, vec4 coord [, float bias] ) 
       vec4 shadow2DProj (sampler2DShadow sampler, vec4 coord [, float bias] ) 
       vec4 shadow1DLod (sampler1DShadow sampler, vec3 coord, float lod) 
       vec4 shadow2DLod (sampler2DShadow sampler, vec3 coord, float lod) 
       vec4 shadow1DProjLod(sampler1DShadow sampler, vec4 coord, float lod)
       vec4 shadow2DProjLod(sampler2DShadow sampler, vec4 coord, float lod)
       // 使用纹理坐标库对绑定到采样器的深度纹理进行深度比较查找
      
    8. 片段处理功能

      片段处理函数只能在片段着色器中使用。

      派生物可能在计算上昂贵和/或数值上不稳定。因此,一个OpenGL实现可以通过使用快速但不完全准确的导数计算来近似真实的导数。

      使用前向/后向差分来指定导数的预期行为

      GL实现可以使用上述或其他方法来执行计算,但需满足以下条件:

      1. 该方法可以采用分段线性逼近。这种线性近似意味着高阶导数,dFdx(dFdx(x))及以上,是没有定义的。
      2. 该方法可以假设计算的函数是连续的。因此,在非均匀条件体中的导数是没有定义的。
      3. 每个片段的方法可能不同,这取决于该方法可能因窗口坐标而异,而不是屏幕坐标而异的约束。OpenGL 1.4规范3.1节中描述的不变性要求对于导数计算是宽松的,因为该方法可能是片段定位的函数。

      其他需要但不是必需的属性有:

      1. 函数应该在原语内部计算(内插,而不是外推)。
      2. dFdx的函数应该在保持y恒定的情况下求值。dFdy的函数应该在保持x不变的情况下求值。然而,混合高阶导数,如dFdx(dFdy(y))和dFdy(dFdx(x))是没有定义的。
      3. 常变量的导数应该是0。

      在一些实现中,可以通过提供GL提示来获得不同程度的导数精度,允许用户在图像质量和速度之间进行权衡。

      语法含义
      genType dFdx (genType p)使用对输入参数p的局部差分返回x中的导数。
      genType dFdy (genType p)使用对输入参数p的局部差分返回y的导数。
      genType fwidth (genType p)使用对输入参数p的局部差分,返回x和y的绝对导数的和
    9. 噪声功能

      噪声函数对片段和顶点着色器都是可用的。它们是随机函数,可用于增加视觉复杂性。以下噪声函数返回的值看起来是随机的,但不是真正随机的。下列噪音函数的定义是:

      1. 返回值(s)总是在[-1.0,1.0]范围内,并至少覆盖范围[-0.6,0.6],用a
        Gaussian-like分布。
      2. 返回值的总体平均值为0.0
      3. 它们是可重复的,因为特定的输入值总是会产生相同的返回值
      4. 在旋转下,它们在统计上是不变的。无论域如何旋转,它都具有相同的统计特性
      5. 它们在平移下具有统计不变性。无论如何转换域,它都具有相同的统计特征
      6. 他们通常在翻译时给出不同的结果
      7. 空间频率狭窄集中,集中在0.5到1.0之间的某个地方
      8. 他们是C的一次方,连续无处不在
      语法含义
      float noise1 (genType x)返回基于输入值x的1D噪声值
      vec2 noise2 (genType x)返回基于输入值x的2D噪声值
      vec3 noise3 (genType x)返回基于输入值x的3D噪声值
      vec4 noise4 (genType x)返回基于输入值x的4D噪声值
  7. 着色语言语法

    语法由词法分析的输出提供。从词法分析返回的标记是

    ATTRIBUTE CONST BOOL FLOAT INT
    BREAK CONTINUE DO ELSE FOR IF DISCARD RETURN
    BVEC2 BVEC3 BVEC4 IVEC2 IVEC3 IVEC4 VEC2 VEC3 VEC4
    MAT2 MAT3 MAT4 IN OUT INOUT UNIFORM VARYING
    CENTROID
    MAT2X2 MAT2X3 MAT2X4
    MAT3X2 MAT3X3 MAT3X4
    MAT4X2 MAT4X3 MAT4X4
    SAMPLER1D SAMPLER2D SAMPLER3D SAMPLERCUBE SAMPLER1DSHADOW SAMPLER2DSHADOW
    STRUCT VOID WHILE
    IDENTIFIER TYPE_NAME FLOATCONSTANT INTCONSTANT BOOLCONSTANT
    FIELD_SELECTION
    LEFT_OP RIGHT_OP
    INC_OP DEC_OP LE_OP GE_OP EQ_OP NE_OP
    AND_OP OR_OP XOR_OP MUL_ASSIGN DIV_ASSIGN ADD_ASSIGN
    MOD_ASSIGN LEFT_ASSIGN RIGHT_ASSIGN AND_ASSIGN XOR_ASSIGN OR_ASSIGN
    SUB_ASSIGN
    LEFT_PAREN RIGHT_PAREN LEFT_BRACKET RIGHT_BRACKET LEFT_BRACE RIGHT_BRACE DOT
    COMMA COLON EQUAL SEMICOLON BANG DASH TILDE PLUS STAR SLASH PERCENT
    LEFT_ANGLE RIGHT_ANGLE VERTICAL_BAR CARET AMPERSAND QUESTION
    INVARIANT
    

    下面根据上面的标记描述OpenGL着色语言的语法。

     IDENTIFIER
    primary_expression:
     variable_identifier
     INTCONSTANT
     FLOATCONSTANT
     BOOLCONSTANT
     LEFT_PAREN expression RIGHT_PAREN
     postfix_expression:
     primary_expression
     postfix_expression LEFT_BRACKET integer_expression RIGHT_BRACKET
     function_call
     postfix_expression DOT FIELD_SELECTION
     postfix_expression INC_OP
     postfix_expression DEC_OP integer_expression:
     expression
    function_call:
     function_call_or_method function_call_or_method:
     function_call_generic
     postfix_expression DOT function_call_generic function_call_generic:
     function_call_header_with_parameters RIGHT_PAREN
     function_call_header_no_parameters RIGHT_PAREN function_call_header_no_parameters:
     function_call_header VOID
     function_call_header function_call_header_with_parameters:
     function_call_header assignment_expression
     function_call_header_with_parameters COMMA assignment_expression
    function_call_header:
     function_identifier LEFT_PAREN // Grammar Note: Constructors look like functions, but lexical analysis recognized most of them as
    // keywords. They are now recognized through “type_specifier”. function_identifier:
     type_specifier
     IDENTIFIER
     FIELD_SELECTION
     unary_expression:
     postfix_expression
     INC_OP unary_expression
     DEC_OP unary_expression
     unary_operator unary_expression
    // Grammar Note: No traditional style type casts.
    unary_operator:
     PLUS
     DASH
     BANG
     TILDE // reserved // Grammar Note: No '*' or '&' unary ops. Pointers are not supported.
    multiplicative_expression:
     unary_expression
     multiplicative_expression STAR unary_expression
     multiplicative_expression SLASH unary_expression
     multiplicative_expression PERCENT unary_expression // reserved
    additive_expression:
     multiplicative_expression
     additive_expression PLUS multiplicative_expression
     additive_expression DASH multiplicative_expression
    shift_expression:
     additive_expression
     shift_expression LEFT_OP additive_expression // reserved
     shift_expression RIGHT_OP additive_expression // reserved relational_expression:
     shift_expression
     relational_expression LEFT_ANGLE shift_expression
     relational_expression RIGHT_ANGLE shift_expression
     relational_expression LE_OP shift_expression
     relational_expression GE_OP shift_expression
     equality_expression:
     relational_expression
     equality_expression EQ_OP relational_expression
     equality_expression NE_OP relational_expression
    and_expression:
     equality_expression
     and_expression AMPERSAND equality_expression // reserved exclusive_or_expression:
     and_expression
     exclusive_or_expression CARET and_expression // reserved inclusive_or_expression:
     exclusive_or_expression
     inclusive_or_expression VERTICAL_BAR exclusive_or_expression // reserved logical_and_expression:
     inclusive_or_expression
     logical_and_expression AND_OP inclusive_or_expression
    logical_xor_expression:
     logical_and_expression
     logical_xor_expression XOR_OP logical_and_expression
    logical_or_expression:
     logical_xor_expression
     logical_or_expression OR_OP logical_xor_expression
    conditional_expression:
     logical_or_expression
     logical_or_expression QUESTION expression COLON assignment_expression
    assignment_expression:
     conditional_expression
     unary_expression assignment_operator assignment_expression
    assignment_operator:
     EQUAL
     MUL_ASSIGN
     DIV_ASSIGN
     MOD_ASSIGN // reserved
     ADD_ASSIGN
     SUB_ASSIGN
     LEFT_ASSIGN // reserved
     RIGHT_ASSIGN // reserved
     AND_ASSIGN // reserved
     XOR_ASSIGN // reserved
     OR_ASSIGN // reserved
     expression:
     assignment_expression
     expression COMMA assignment_expression
    constant_expression:
     conditional_expression
    declaration:
     function_prototype SEMICOLON
     init_declarator_list SEMICOLON function_prototype:
     function_declarator RIGHT_PAREN function_declarator:
     function_header
     function_header_with_parameters function_header_with_parameters:
     function_header parameter_declaration
     function_header_with_parameters COMMA parameter_declaration
    function_header:
     fully_specified_type IDENTIFIER LEFT_PAREN
    parameter_declarator:
     type_specifier IDENTIFIER
     type_specifier IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET
    parameter_declaration:
    type_qualifier parameter_qualifier parameter_declarator
     parameter_qualifier parameter_declarator
     type_qualifier parameter_qualifier parameter_type_specifier
     parameter_qualifier parameter_type_specifier
    parameter_qualifier:
     /* empty */
     IN
     OUT
     INOUT
    parameter_type_specifier:
     type_specifier init_declarator_list:
     single_declaration
     init_declarator_list COMMA IDENTIFIER
     init_declarator_list COMMA IDENTIFIER LEFT_BRACKET RIGHT_BRACKET
     init_declarator_list COMMA IDENTIFIER LEFT_BRACKET constant_expression
     RIGHT_BRACKET
     init_declarator_list COMMA IDENTIFIER LEFT_BRACKET
     RIGHT_BRACKET EQUAL initializer
     init_declarator_list COMMA IDENTIFIER LEFT_BRACKET constant_expression
     RIGHT_BRACKET EQUAL initializer
     init_declarator_list COMMA IDENTIFIER EQUAL initializer single_declaration:
     fully_specified_type
     fully_specified_type IDENTIFIER
     fully_specified_type IDENTIFIER LEFT_BRACKET RIGHT_BRACKET
     fully_specified_type IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET
     fully_specified_type IDENTIFIER LEFT_BRACKET RIGHT_BRACKET EQUAL initializer
     fully_specified_type IDENTIFIER LEFT_BRACKET constant_expression
     RIGHT_BRACKET EQUAL initializer
     fully_specified_type IDENTIFIER EQUAL initializer
     INVARIANT IDENTIFIER // Vertex only. // Grammar Note: No 'enum', or 'typedef'. fully_specified_type:
     type_specifier
     type_qualifier type_specifier type_qualifier:
     CONST
     ATTRIBUTE // Vertex only.
     VARYING
     CENTROID VARYING
     INVARIANT VARYING
     INVARIANT CENTROID VARYING
     UNIFORM type_specifier:
     type_specifier_nonarray
     type_specifier_nonarray LEFT_BRACKET constant_expression RIGHT_BRACKET type_specifier_nonarray:
     VOID
     FLOAT
     INT
     BOOL
     VEC2
     VEC3
     VEC4
     BVEC2
     BVEC3
     BVEC4
     IVEC2
     IVEC3
     IVEC4
     MAT2
     MAT3
     MAT4
     MAT2X2
     MAT2X3
     MAT2X4
     MAT3X2
     MAT3X3
     MAT3X4
     MAT4X2
     MAT4X3
     MAT4X4
     SAMPLER1D
     SAMPLER2D
     SAMPLER3D
     SAMPLERCUBE
     SAMPLER1DSHADOW
     SAMPLER2DSHADOW
     struct_specifier
     TYPE_NAME
     struct_specifier:
     STRUCT IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE
     STRUCT LEFT_BRACE struct_declaration_list RIGHT_BRACE struct_declaration_list:
     struct_declaration
     struct_declaration_list struct_declaration struct_declaration:
     type_specifier struct_declarator_list SEMICOLON struct_declarator_list:
     struct_declarator
     struct_declarator_list COMMA struct_declarator
    struct_declarator:
     IDENTIFIER
     IDENTIFIER LEFT_BRACKET constant_expression RIGHT_BRACKET initializer:
     assignment_expression
    declaration_statement:
     declaration
    statement:
    compound_statement
     simple_statement
    // Grammar Note: No labeled statements; 'goto' is not supported.
    simple_statement:
     declaration_statement
     expression_statement
     selection_statement
     iteration_statement
     jump_statement
    compound_statement:
     LEFT_BRACE RIGHT_BRACE
     LEFT_BRACE statement_list RIGHT_BRACE statement_no_new_scope:
     compound_statement_no_new_scope
     simple_statement
    compound_statement_no_new_scope:
     LEFT_BRACE RIGHT_BRACE
     LEFT_BRACE statement_list RIGHT_BRACE statement_list:
     statement
     statement_list statement
    expression_statement:
     SEMICOLON
     expression SEMICOLON selection_statement:
     IF LEFT_PAREN expression RIGHT_PAREN selection_rest_statement
    selection_rest_statement:
     statement ELSE statement
     statement
    // Grammar Note: No 'switch'. Switch statements not supported.
    condition:
     expression
     fully_specified_type IDENTIFIER EQUAL initializer iteration_statement:
     WHILE LEFT_PAREN condition RIGHT_PAREN statement_no_new_scope
     DO statement WHILE LEFT_PAREN expression RIGHT_PAREN SEMICOLON
     FOR LEFT_PAREN for_init_statement for_rest_statement RIGHT_PAREN statement_no_new_scope
    for_init_statement:
     expression_statement
     declaration_statement
    conditionopt:
     condition
     /* empty */
    for_rest_statement:
     conditionopt SEMICOLON
     conditionopt SEMICOLON expression
    jump_statement:
     CONTINUE SEMICOLON
     BREAK SEMICOLON
     RETURN SEMICOLON
     RETURN expression SEMICOLON
     DISCARD SEMICOLON // Fragment shader only. // Grammar Note: No 'goto'. Gotos are not supported.
    translation_unit:
     external_declaration
     translation_unit external_declaration external_declaration:
     function_definition
     declaration
     function_definition:
     function_prototype compound_statement_no_new_scope
    

问题

  1. 像下面这样有模糊的函数参数匹配是错误的吗?

    void foo(int);
    void foo(float);
    foo(3);
    

    解析:没有,如果有精确的匹配,而不进行转换,则不会出现歧义性错误。

  2. 像下面这样有模糊的函数参数匹配是错误的吗?

    void foo(float, int)
    void foo(int, float)
    foo(2, 3);
    

    解析:是的。这将导致一个不明确的调用错误。任何时候,只要有两种通过转换进行匹配的方法,而且不进行转换就没有精确的匹配,就应该引发错误。

  3. 如果三元(?:)运算符的第二和第三操作数的类型不匹配,会发生什么?它们应该被转换以相互匹配,还是应该延迟转换以匹配整个三元表达式的使用方式(比较困难)?

    解析:它们必须进行转换以相互匹配,这样表达式的类型就由表达式决定。

  4. 如果dFdx、dFdy和fwidth的参数是常量表达式,为了在常量初始化中使用它们,我们希望它们返回0。规范目前理论上允许这些衍生物的非零。

    备选解决方案:说它们必须返回0,理论上没有人会实现非0,或者如果他们这样做了,着色器应该仍然在新的规则下工作.

    替代解决方案:在初始化项中不允许这些。

    解析:假设它们在初始化时只返回0。

  5. 我们应该添加明显缺失的矩阵构造函数吗?

    替代解决方案:对于剩余的Cm,保持未定义

    另一种解决方案:要求P >= N和Q >= m,也就是说,你只能使用更小的矩阵。

    分辨率:
    matPxQ m;

    matNxM Cm = matNxM(m)

    对于m和Cm中同时存在的每个2D索引[i][j],将Cm[i][j]设为m[i][j]。其余所有Cm[i][j]设为单位矩阵。

  6. 我们应该添加矩阵构造函数来允许行构造吗?

    解析:推迟。

  7. 在使用==或!=操作符之前,我们应该要求数组操作数显式调整大小吗?

    解析:是的

  8. 我们应该要求数组操作数显式大小之前,被使用分配?

    解析:是的。

  9. 可以使用无大小的构造函数吗?列出的参数的数量意味着什么是很清楚的。
    这里有两条路可走。这就是,“float[]”是指一个隐式大小的数组,还是我们想保留它作为将来发布的引用?如果隐式大小数组,则“float”是一个显式大小为2的数组,就像“float a[] = float2”已经是。我们甚至可以允许“float a[] = float;”。

    解析:是的,可以使用无大小的构造函数,也可以使用通过初始化器调整大小的无大小声明。将来我们应该为引用提出一种与类型正交的不同语法(方括号将是特定于数组的引用语法)。注意,这不是创建隐式大小数组的另一种方法,因为数组会立即以无法更改的显式大小结束。

    明确未设置大小数组的初始化器和赋值器之间的新区别,因为我们已经说过,初始化器可以初始化语法上看起来像未设置大小数组的东西,而赋值器却不能。

  10. 函数参数可以是非大小数组吗?函数可以在内部克隆,和/或通过引用在内部传递的数组(仍然满足非混叠和通过复制语义在外部传递)。如果我们讨论通过引用语义传递,讨论需要传入不同的函数体,例如统一数组和全局数组,因为对象必须是不同的,那么这种内部函数克隆将再次出现。

    解析:推迟。需要在链接时进行编译。可能我们正处在一个函数体接受不同类型和大小的数组的路径上,而那个空方括号(无大小数组)意味着接受多个大小,而不是接受对数组的引用。

  11. 我们是否应该为方阵使用矩阵别名,例如:摔打的mat2x2和mat2是一样的?

    解析:是的。

  12. 我们是否应该为向量使用矩阵别名,例如:摔打的mat1x2与vec2相同?我们要么需要这个,要么需要一个内建函数来用一个行向量乘以一个列向量,然后返回一个矩阵。

    解析 :不,不别名向量。它不是一个真正的别名,因为向量是行向量还是列向量是模糊的。改为添加内置函数“matN outerProduct(vecN, vecN)”

  13. 非方阵类型中的第一个数字,即行数或列数。行数是最直观的。列的数量与现有的索引顺序更加一致。

    备选解决方案:第一个数字是行数。无论如何,我们都想朝这个方向前进……主要独立于抽象的布局。我们可以添加一个构造来请求行主顺序,这表示索引的意思,但是我们可以通过表示第一个数字是行数来保持类型声明的相同。

    解析:永远不要添加模式来请求基于行的操作,也不要转向更加基于行的语言。第一个数字表示列数。

  14. 我们是否应该添加一个从矩阵中选择一行的方法,例如m行(i)?

    解析:推迟这

  15. 矩阵产品内置的真正拼写应该是什么?

    解析:outerProduct

  16. 存储一个矩阵所需的属性位置的数量是行数、列数还是其中的最大值?

    备选解决方案:最大,允许实现选择最有效地移动数据。

    分辨率:列数;由于顶点数组的当前状态,列到槽的映射仍然是公开的。

  17. 我们要在多大程度上定义质心的位置?“近”似乎有点含糊。

    解析 :它至少必须在原语和像素的交集中,或者在原语中的一个片段样本中。优选地,它是交点的质心。在这个交点内的样本质心也是一个很好的选择。在交叉区域内靠近这些质心的样本位置也是不错的选择。

  18. 我们能不能像在ES中那样,设置一个静态递归限制?在桌面规范中,我们刚刚禁止了动态递归,但我认为最好也禁止静态递归。如果到目前为止还没有人支持静态递归,我们可以这样做。

    解析:不允许像ES那样的静态递归。

  19. 我们需要加上转置吗?它应该是一个就地转置方法,还是一个返回函数转置的方法,还是一个返回函数转置的内置函数?

    解析 :增加内置功能

    mat2 transpose(in mat2)
    mat3 transpose(in mat3)
    mat4 transpose(in mat4)
    mat2x3 transpose(in mat3x2)
    mat3x2 transpose(in mat2x3)
    mat2x4 transpose(in mat4x2)
    mat4x2 transpose(in mat2x4)
    mat3x4 transpose(in mat4x3)
    mat4x3 transpose(in mat3x4)
    

    并保留一个. '()方法以备将来使用。

  20. 应用程序能否查询统一在链接时设置的初始化器值?

    解决方案:是的,使用现有的入口点进行统一查询,因此不需要采取任何操作。

  21. 质心变异有导数吗?

    替代解决:质心变异的导数是未定义的。
    分辨率:说它们是有定义的,但不那么准确。发送清单中条件5的更新。备选解决方案:导数和以前一样准确,可能通过传递一个单独的非质心变化用于导数来实现。对于参数中的复杂表达式可能会比较困难。

  22. 我们应该要求着色器包含# 120版本来使用1.20的特定特性吗?这可能会降低可移植性,因为代码可能会在不需要120个特性的着色器上使用它。另一方面,它为小的非向后兼容的改变打开了大门,这通常是有益的,比如在新的关键字前面没有“__”。

    解析:是的。需要#版本120来启用问题20A

  23. 我们需要一个质心版本的gl_FragCoord吗?

    解析:没有。至少要清楚现有的gl_FragCoord不是质心。添加一个新的形心碎片-coord是有意义的,但到目前为止似乎是边际使用,所以我们还不必这样做。

  24. 声明一个变量隐藏相同名称的函数吗?1.10规范明确表示这种情况不会发生。

    交替决议:没有。这将是一个向后不兼容的变化,可能会打破一些着色器。例如,像float sin = sin(expr);float r = sin * sin;是有用的,可能已经被写。解析:是的。使用#version 120允许我们以这种微妙的方式破坏兼容性。代码仍然可以在本地声明变量并使用它,即使变量名与函数名匹配。它只是不能继续在同一作用域中调用同名的函数。如果函数和变量都在同一个范围内声明,则会出现重新声明错误。

  25. 声明一个结构类型名称是否隐藏变量和所有相同名称的函数?(在相同的作用域内重声明相同的变量名当然是重声明错误。)这就是1.10规范的目的。

    解析:是的。这与现有的规范一致,避免了新构造函数和同名现有函数之间的混淆,并为将来的备用构造函数签名保留了一个名称空间。

  26. 声明一个函数是否隐藏了一个结构类型名称和它的构造函数(具有相同的名称),在嵌套范围和在相同的范围?

    解决方法:不允许局部函数声明。在全局作用域,同时具有相同名称的函数和结构是一个重定义错误。交替决议:是的。必须隐藏构造函数。既隐藏构造函数又不隐藏类型是没有意义的,因此必须同时隐藏两者。

  27. 在嵌套作用域中声明一个函数,是否可以解除在中间作用域中被结构类型声明隐藏的函数定义?

    解决方法:不允许局部函数声明。交替决议:是的。对于这样做以及这样做有什么用,几乎没有其他有效的解释。它只是在嵌套作用域内执行您想要的操作,并为所有函数定义使用单个全局空间。

  28. 声明一个函数是否取消隐藏的函数定义已经隐藏的结构类型声明在同一作用域?

    解决方法:不允许局部函数声明。交替决议:没有。使在同一作用域中声明与结构类型具有相同名称的函数为重新声明错误。这是一件很模糊的事情。它有一个小的机会打破现有的着色器,列如

    void foo(int) { ... }
    void main()
    {
     struct foo { float f; }; // legal
     void foo(int); // suggested to be illegal; this scope has a foo()
    }
    
  29. 声明一个函数是否隐藏了相同名称的变量?

    解析:仅在全局范围,这是一个重新声明错误。对于局部作用域,不允许局部函数声明。用户不能将内置变量重声明为函数名,因此这不是问题。

    交替决议:没有。用变量来隐藏函数是不对称的,用函数来隐藏变量是不对称的。它也是向后不兼容的,虽然可能很少在真实的着色器中做。

    交替决议:是的。如果我们仍然允许局部声明,如果我们改变一些东西,让变量隐藏函数,以保持一切对称。这将是同一范围内的重新声明错误。

  30. 变量声明是否取消隐藏了被结构类型隐藏的函数?例如,

    void foo(int) { ... }
    void main()
    {
     struct foo { float f; }; // legal, hides 'void foo(int)'
     {
     bool foo; // legal, hides 'struct foo'
     foo(2); // ?? is 'void foo(int)' visible again?
     }
    }
    

    解析:没有。

  31. 我们是否应该添加入口点来封装加载矩阵属性,这样系统就可以真正隐藏内部主行和主列的内存顺序?

    解析:没有。我们想这样做,但是它需要顶点属性数组的支持,到目前为止只有四个组件宽,所以还没有能力表示整个矩阵。注意,这将包含在伴随该语言规范的API扩展中。

  32. 是否有API机制来设置质心提示?“最快”允许质心位于像素中心(在原语之外)吗?

    解决方案:不需要增加特定的新机制。只需使用现有的导数提示机制。

  33. 需要澄清的是,gl_FragCoord也是用于多次采样的示例之一。样本与varyings相同?

    解析:是的

  34. 内置函数和名称是在全局作用域内,还是在比全局作用域更“外部”的作用域内?

    可能的解决方案:不。把内建物放在全球范围内。这意味着不能在全局范围内定义与内置函数同名的变量或结构。这意味着着色器全局变量污染了内置函数的名称空间(可在将来通过使用“gl”前缀寻到,或为“gl”情况设置名称空间,必须决定用户是否可以提供新的gl名称签名)。重新声明一个内置函数不会隐藏同名的其他函数。我们仍然允许提供一个新的内置函数体,并且仍然希望为重新声明的内置函数找到这样的体。

    解析:是的。内建物在更外面的范围内。这意味着您可以定义一个与内建函数同名的全局变量,这将隐藏具有该名称的所有内建函数。重新声明一个内置函数会隐藏该名称的内置函数,这会破坏向后兼容性;所有这样的签名必须有正文。对于编码器覆盖了一个签名而没有覆盖其他签名,并且仍然调用了其他签名中的一个的大多数情况,这将给出一个错误,因为对于大多数内置函数,我们的转换规则不能匹配一个原型和另一个原型的参数。例外是小于(整型参数)和小于(浮点参数),类似的还有greaterThan、lessThan、greaterThanEqual、equal和notEqual

  35. 我们想要保留哪些新的关键字

    解析:我们可以保留新的关键字,因为它们被#version保护。这些包括低lowp,mediumphighpprecision

  36. 我们需要支持基本的不变性。

    解析:复制OpenGL ES的不变关键字,但只允许它在用户变量、来自顶点着色器的内置变量输出和特殊变量gl_Position和gl_PointSize上使用。

  37. 我们是否应该允许函数返回值不变,以便在不同编译单元中的函数可以在不变计算中使用?

    解析:推迟。这是个好计划。对于1.2版本,如果控制流和数据流完全在一个编译单元中,编译器只能计算出它们。

  38. 如果片段着色器在顶点,是否需要声明为不变的?他们可以选择吗?

    解决方案:不变条件必须在两边匹配。插值计算可以跨边分割,两边都需要以不变的方式进行计算。如果它们不匹配,那就是错误。不将其报告为错误意味着要么在没有必要的情况下对顶点一侧进行不变量计算,要么在需要时不进行不变量计算。

  39. 如果不同的编译单元对于相同的隐式大小数组有不同的隐含大小,会发生什么?

    解析 :使用最大值,不要给出错误。

  40. 我们应该移除嵌入的结构来匹配ES吗?

    解析:是的。这消除了将来可能出现的名称空间问题,在这种情况下,实名空间操作符(例如“::”)嵌套结构的名称可以限定在结构的名称空间内,但现在它们必须限定在与包含结构相同的范围内。

总结
  1. 不允许局部函数声明
  2. 如果在同一范围内已经有同名的函数、变量或结构类型,则声明变量或结构类型是重声明错误
  3. 如果在同一作用域中已经有同名的变量或结构类型,则声明函数是重声明错误
  4. 函数可以在同一作用域中声明多次
  5. 变量或结构类型的声明在所有外部范围中隐藏了同名的所有变量、函数或结构类型(及其构造函数)
  6. 隐藏的功能保持隐藏
  7. 不能有局部函数声明
  8. 不能在同一作用域内使用相同名称的函数和变量
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值