GLSL-语法基础

3.1 字符集

GLSL用到的字符集是ASCII码的子集,主要包含下面的几部分:

  1. 字母 a-z,A-Z,以及下划线“_”。
  2. 数字 0-9
  3. 符号 .+-/*%<>[]{}()|$~=!:;,?
  4. 预处理器专用符号 #
  5. 空白符,包括各种回车、换行、TAB等等

该字符集不包含反斜杠**,也不包含任何字符或字符串。
一般来说,GLSL是大小写敏感的。

3.2 Source Strings

不管是Vertex Shader还是Fragment Shader,其实都有一段对应的代码,称为Source Strings,它包含多个string,每个string一行。

对于本版本的GLSL来说,每个Shader对应着一个可编程单元,一个Vertex Shader对应着一个Fragment Shader,二者连接起来组成一个program

3.3 编译的逻辑阶段

编译程序使用C++标准的一个子集。
Vertex Shader和Fragment Shader在各自编译之后连接到一起。

编译的步骤如下:

  1. .glsl文件中得到Source String
  2. Source Strings被转成一系列的预处理Token,这些Token包括预处理行号、预处理标识、预处理操作。注释被替换成空格。保留换行。
  3. 预处理器执行。执行命令,执行宏扩展。
  4. 预处理Token被转换成Token
  5. 空白符、换行符被丢弃。
  6. 根据GLSL语法进行语法分析。
  7. 根据GLSL语义规则进行语义检查。
  8. Vertex Shader和Fragment Shader连接到一起,丢弃没有被二者同时使用的varying变量。
  9. 生成二进制文件。

3.4 预处理器

作为编译的其中一个步骤,预处理器会处理source strings。

预处理指令以#开头,#号之前不能有除了空白字符之外的任何字符。每一个指令独占一行。
预处理指令只能使用上面列出的指令,使用其他未定义指令会报错。

完整的预处理器指令如下:

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

下面的这个操作符也是合法的:

defined

#define and #undef

这两个和C++中的用法完全一样,不做过多说明

#if, #ifdef, #ifndef, #else, #elif, and #endif

这几个也基本和C++的用法一样,不过有下面几点差别。

1. #if和#elif只能判断int类型,0为false,非0为true
2. 不支持string类型

#pragma

编译指示。用来控制编译器的一些行为。

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

在开发和调试时可以设置为off,默认设为on。

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

在开发和调试时可以打开debug选项,以便获取更多的调试信息。默认设为off。

#version

每一个编译单元都要指定GLSL的版本,如下:

#version number

一般默认即可。这个命令必须放到编译单元的最前面,其前面只能有注释或空白,不能有其他字符。

#extension

如果想使用GLGL默认不支持的操作,则必须启用对应的扩展,启用一个扩展可以使用下面的命令:

#extension : behavior
#extension all : behavior

其中,extension_name是扩展的名称,all是指所有的编译器支持的扩展。
behavior是指对该扩展的具体操作。比如启用、禁用等等。详情如下:

behavior 作用
require 启用该扩展。如果不支持,则报错。
enable 启用该扩展。如果不支持,则会警告。extension_name是all的时候会报错。
warn 启用该扩展。但是会检测到所有使用该扩展的地方,提出警告。
disable 禁用该扩展。如果该扩展不被支持,则提出警告。

默认是不会启用任何扩展,就像是执行了下面的命令:

#extension all : disable

对于每一个被支持的扩展,都有一个对应的宏定义,我们可以用它来判断编译器是否支持该扩展。

#ifdef OES_extension_name
#extension OES_extension_name : enable
    // code that requires the extension
#else
    // alternative code
#endif

一些预定义的变量

除此之外,还预定义了一些变量:

  • LINE :int类型,当前的行号,也就是在Source String中是第一行
  • FILE :int类型,当前Source String的唯一ID标识
  • VERSION :int类型,GLGL的版本
  • GL_ES :对于嵌入式系统(Embed System,简称 ES),它的值为1,否则为0

所有的以两个下划线__开头的变量都是系统保留的,不允许私自定义和篡改。

操作符

优先级 类型 操作符 结合性
1 括号 ()
2 一元运算符
defined + - ~ !

| 从右往左 |
| 3 | 乘除法 | * / % | 从左往右 |
| 4 | 加减法 | + - | 从左往右 |
| 5 | 位运算 移位 | << >> | 从左往右 |
| 6 | 大小关系 | < > <= >= | 从左往右 |
| 7 | 相等性判断 | == != | 从左往右 |
| 8 | 位运算 与 | & | 从左往右 |
| 9 | 位或算 非 | ^ | 从左往右 |
| 10 | 位或算 或 | | | 从左往右 |
| 11 | 逻辑与 | && | 从左往右 |
| 12 | 逻辑或 | || | 从左往右 |

defined操作符一般有下面两种使用方式:

defined identifier
defined ( identifier )

注释

和Java里的注释没啥区别。主要有下面两种注释方法。

// 我是注释
/* 我是注释 */

Tokens

Source String会被转成一系列的Tokens。可以这么理解,代码中的每一个单词都属于某一种Token。比如关键词、数字、变量等等。GLSL中,Token主要有下面几种:

Token 解释 举例
keyword 关键词 if elif ...
identifier 标识符 自定义的函数名、变量名 ...
integer-constant int常量 1 2 3 ...
float-constant float常量 1.1 1.2 1.3 ...
operator 操作符 + - * / ...

关键词

列举一下GLSL中的关键词,这些全部是系统保留的,不可私自篡改。

attribute const uniform varying
break continue do for while
if else
in out inout
float int void bool true false
lowp mediump highp precision invariant
discard return
mat2 mat3 mat4
vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4
sampler2D samplerCube
struct

下面的这些也被系统预留了,使用它们会报错。

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

除此之外,所有的以"__"开头的变量全部是预留的,自定义的变量不能以“__”开头。

标识符

标识符其实就是指用户自定义的变量名、函数名、结构体名等等。

变量名可以由下面的字符组成:

_ 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
0 1 2 3 4 5 6 7 8 9

用户可以随便定义标识符,但是不能以gl_开头,因为以gl_开头的都是系统预留的。