当前位置:   article > 正文

渲染世界的Opengl<2>着色器基础-着色器语言概述_着色器 #pragma

着色器 #pragma

1.OpenGL的可编程管线
(1)顶点着色阶段
将接受你在顶点缓存对象中给出的顶点数据。独立处理每个顶点。必须步骤,并且必须绑定一个着色器
(2)细分着色阶段
可选阶段。与应用程序中显式指定几何图元的方法不同,会在Opengl管线内部生成新的几何体。
(3)几何着色阶段
可选的阶段,会在Opengl管线内部对所有的几何图元进行修改。这个阶段会作用于每一个独立的几何图元。从输入图元生成更多的几何体,改变图元的类型或者放弃所有的几何体。
(4)片元着色阶段
OpenGL着色管线的最后一个部分。处理OpenGL光栅化之后生成的独立片元,而且必须绑定一个着色器。在这个阶段中计算一个片元的颜色和深度值,然后传递到管线的片元测试和混合的模块。
(5)计算着色阶段
并不是图形管线当中的一部分,而是在程序中相对独立的一个阶段。计算着色阶段处理的并不是顶点和片元这类图形数据,而是应用程序给定范围的内容。计算着色器在应用程序中可以处理其他着色器程序所创建和使用的缓存数据。

OpenGL着色器阶段之间的数据传输方式:
示例程序:

#version 330 core
in vec4 vPosition;
in vec4 vColor;

out vec4 color;

uniform mat4 ModleloViewProjectMatrix;
void main()
{
    color = vcolor;
    gl_Position = ModleloViewProjectMatrix*vPosition;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

GLSL的main()函数没有任何参数,在某个着色阶段中输入和输出的所有数据都是通过着色器中的特殊全局变量来传递的。
OpenGL会使用输入和输出变量来传输着色器数据。
in变量将数据拷贝到着色器中。
out变量将着色器的内容拷贝出去。
以上变量值会在O喷GL每一次执行着色器的时候更新。
uniform变量不会随着顶点或者片元的变化而变化,他对于所有的几何体图元的值都一样。除非应用程序对他进行了更新。

2.OpenGL着色语言概述
关于GLSL的语言规范,和C++非常相像,具备C++的典雅和严谨的优点。下面对于GLSL语言的规范,权当复习C++的基础规范吧。

(1)变量的声明
GLSL作为一种强类型语言,所有变量必须事先声明,并且要给出变量的类型。

变量名命名规范 :可以使用字母数字以及下划线字符组成变量名称。但是数字和下划线不能作为变量名称的首字符。此外,变量名称不能包含连续的下划线。

GLSL支持的基本数据类型:float,double,int,uint,bool
以上类型都是透明的。内部形式是暴露出来的,因此着色器代码中可以假设其内部的构成方式。与之对应的一部分类型称之为不透明类型,内部形式没有暴露出来,包括采样器,(sample),图像等。

(2)变量的作用域
GLSL作用域规则:
-在任何函数定义之外声明的变量拥有全局作用域,因此对着色器程序中的所有函数都是可见的。
-在一组大括号内生命的变量,只能够在大括号范围内存在。
-循环的迭代自变量只能够在循环体内部起作用。

(3)变量的初始化
所有的变量必须在声明的同时进行初始化。
以及数字后缀“u””U”表示无符号整型。
“f”“F”:浮点数。
“lF”“LF”:double精度的浮点数。
额,上面对于经常摸unity的人有点冗长了~

(4)构造函数
GLSL比C++更加注重类型安全,因此它支持的数值隐式转换更少一些。

可以进行隐式转换的类型=>目标类型:
int=>uint
int、uint=>float
int、uint、float=>double
上面类型转换适用于这些类型的标量、向量以及矩阵。转换不会改变向量或者矩阵本身的形式,也不会改变他们组成元素数量。类型转换不能够应用于数组以及结构体上。
所有其他数值转换都需要提供显式的类型转换构造函数。这里构造函数的意义与C++语言类似。示例如下:

float f=10.0;
int ten=int(f);
  • 1
  • 2

(5)聚合类型
GLSL的基本类型可以进行合并,从而可以和OpenGL的基本类型进行匹配,以及简化计算过程的操作。首先GLSL支持2个,3个以及4个分量的向量。每个分量都可以使用bool、int、uint、float和double这些基本类型。此外GLSL也支持float和double类型的矩阵。
向量:
float vec2 vec3 vec4
double dvec2 dvec3 dvec4
int ivec2 ivec3 ivec4
uint uvec2 uvec3 uvec4
boll bvec2 bvec3 bvec4
矩阵 :
mat2 mat3 mat4
mat2x2 mat2x3 mat2x4
dmat2x2 dmat2x3 dmat2x4

vec3 velocity=vec3(0.0,2.0,3.0);
ivec3 steps=ivec3(velocity);
  • 1
  • 2

注意,向量的构造函数可以用来截短或者加长一个向量,并且可以直接将标量值传递给向量。

vec3 white=vec3(1.0);//(1,1,1)
vec4 translucent =vec4(white,0.5);
  • 1
  • 2

矩阵的构建方式类似,可以把它初始为一个对角矩阵或者完全填充的矩阵。对于对角矩阵,只需要向构造函数传递一个值,矩阵的对角线元素就设置为这个值,其他元素全部设置为0。
例如:

m=mat3(4.0);
  • 1

矩阵也可以通过在构造函数中指定每一个元素的值来构建。传入元素可以使标量和向量的集合,只要给定足够数量的数据即可,每一列的设置方式也遵循这样的原则。此外矩阵的指定需要遵循列主序的原则,也就是说,传入的数据将首先填充列,然后填充行。

mat3  = mat3(1.0,2.0,3.0
            4.0,5.0,6.0
            7.0,8.0,9.0);
vec3 column1=vec3(1,2,3);
vec3 column2=vec3(4,5,6);
vec3 column3=vec3(7,8,9);
mat3  = mat3(column1,column2,column3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(6)访问向量和矩阵当中的元素
向量和矩阵当中元素可以单独访问和设置。
-通过名称进行各个分量的访问:

float red=color.r;
float v_y=velocity.y;
  • 1
  • 2

向量分量的访问符:
与位置相关的分量:(x,y,z,w)
与颜色相关的分量:(r,g,b,a)
与纹理坐标相关的分量:(s,t,p,q)

-通过从0开始的索引进行访问:

float red=color[0];
float v_y=velocity[1];
  • 1
  • 2

O应用O
改变向量中分量的各自分量位置:

color=color.abgr;
  • 1

(7)结构体
GLSL当中可以在逻辑上将不同类型的数据组合到一个结构体当中,结构体可以简化多组数据传入函数的过程。如果定义了一个结构体,那么它会自动创建一个新类型,并且隐式定义一个构造函数,将各种类型的结构体元素作为输入参数。(此处的代码参照C++,几乎一致)

(8)数组
GLSL支持任意类型的数组,包括结构体数组。大小n的数组的元素范围为0-n-1。
可以定义为有大小的,没有大小的。作为GLSL的第一类类型,数据具有构造函数,并且可以作为函数的参数和返回类型。
并且有一个隐式的方法可以返回元素的个数。取长度方法length()。向量和矩阵也可以使用length()方法。向量的长度就是它包含的分量的个数,矩阵的长度是它包含的列的个数。

mat3x4 m;
int c=m.length();
int r=m[0].length();
  • 1
  • 2
  • 3

(9)存储限制符
数据类型可以通过一些修饰符来改变自己的行为。

const:讲一个变量定义为只读模式。如果初始化的时候用一个编译时常量,本身也会成为编译时常量。这种变量必须在声明的时候就进行初始化。
in:设置这个变量为着色器阶段的输入变量。这类输入变量可以是顶点属性或者前一个着色器阶段的输出变量。

out:设置这个变量为着色器阶段的输出变量。定义一个着色器阶段的输出变量 。

buffer:设置应用程序共享的一块可读写的内存。这块内存也作为着色器的存储缓存使用。其指定随后的块作为着色器与应用程序共享的一块内存缓存。这块缓存对于着色器来说是可读和可写的。缓存大小可以在着色器编译和程序连接完成后设置。

shared:设置变量是本地工作组中共享的。他只能用于计算着色器中。只能用于计算着色器当中,可以建立本地工作组内共享的内存。

uniform:设置这个变量为用户应用程序传递给着色器的数据,他对于给定的图元来说是一个常量。uniform修饰符可以制定一个在应用程序设置好的变量,他不会再图元处理的过程中发生变化。uniform变量在所有着色阶段都是共享的。必须定义为全局变量。任何类型的变量都可以设置为uniform变量。着色器无法写入到uniform变量,也无法改变值。
相关函数:

GLint glGetUniformLocation(GLuint program,const char* name);
//返回着色器程序中uniform变量name对应的索引值,name是一个以NULL结尾的字符串,不存在空格。如果name与启用的着色器程序中的所有uniform变量都不相符,或者name是一个内部保留的着色器变量名称,那么返回值为-1。
  • 1
  • 2

利用以上函数得到uniform变量对应的索引值之后,就可以通过glUniform*()或者glUniformMatrix *()系列函数来设置uniform变量的值了。

void glUniform{1234}{fdi ui}(GLint location,TYPE value);
void glUniform{1234}{fdi ui}v(GLint location,GLsizei count,const TYPE*  values);
void glUniformMatrix{234}{fd}v(GLint location,GLsizei count,GLboolean transpose,const GLfloat* values);

//设置与location索引位置对应的uniform变量的值。其中向量形式的函数将会载入count个数据的集合,并且写入location位置的uniform变量。如果location 是数组的起始索引值,那么数组之后的连续count个元素都会载入。对于glUniformMatrix{234}系列函数来说,可以从values中读入对应矩阵维度的数值并且构成矩阵。如果transpose设置为GL_TRUE,那么values中的数据以行主序的顺序读入的,如果是GL_FALSE,那么values中的数据是以列主序的顺序读入的。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.着色器编程语句
(1)算数操作符
和C++几乎一致。
(2)操作符重载
基本的限制条件是:要求矩阵和向量的维度必须匹配。
两个向量相乘的结果是一个逐分量相乘的新向量。
两个矩阵相乘的结果是通常矩阵相乘的结果。

(3)流控制
逻辑控制方式也是if else以及switch case的方式。
与C++不同的是,GLSL不允许在第一个case之前添加语句。

(4)循环语句
for循环可以在循环初始化条件当中声明循环迭代变量。但是此时的迭代变量的作用域只限于循环体内部。

(5)流控制语句
前三个和C++几乎一致。
break:终止循环体的运行,继续执行循环体之外的内容。
continue:终止循环体在当前迭代器的执行,直接跳转到代码块开始部分开始下一次循环。
return:从当前子例程返回,可以带有一个函数返回值。
discard:丢弃当前片元,终止着色器的执行。discard语句只能够用于片元着色器当中。

(6)声明
函数声明的语法和C类似。
必须在使用函数之前找到函数的声明,否则会产生错误。

(7)参数限制符
in:将数据拷贝到函数当中。in是可选的。如果变量没有包含任何访问修饰符,那么默认为in。
const in:将只读数据拷贝到函数当中。
out:从函数当中获取数值。
inout:将数据拷贝到函数中,并且返回函数中修改的数据。

(8)计算不变性
GLSL无法保证在不同的着色器中两个完全相同的计算式会得到一样的结果。
GLSL有两种方式保证各个着色器之间的计算不变性。
I.invariant限制符
可以设置任何着色器的输出变量。可以确保如果两个着色器的输出变量使用相同的表达式,而且变量也是相同值,那么结果必定一致。
可以在着色器中用到某个变量或者内置变量之前的任何位置,对该变量设置关键字:invariant。
如果对于着色器当中所有可变量全部设置为invariance。顶点着色器的预编译命令:

#pragma STDGL invariant[all]
  • 1

但是注意,这样的着色器性能会受到影响。为了保证不变性,通常也会导致GLSL编译器所执行的一些优化工作被迫停止。

II.precise限制符
precise限制符可以设置任何计算中的变量或者函数的返回值。通常在细分着色器中用它来避免造成几何体形状的裂缝。可以设置内置变量,用户变量或者函数的返回值。
在着色器中,关键字precise的使用的影响:表达式不能够在使用两种不同的乘法命令同时来参与计算。比如加乘混合运算,利用内置函数fma()代替。

precise out float result;
……
float f=c*d;
float result=fma(a,b,f);
  • 1
  • 2
  • 3
  • 4

(9)着色器的预处理器
GLSL没有文件包含的命令。(#include)
宏定义:

#define LPos[n] gl_LightSource[(n)].position
//与C语言一致的宏定义方式。
#undef LPos
//取消之前定义过的宏。
  • 1
  • 2
  • 3
  • 4

GLSL预处理器当中的预定义宏:
_ LINE_:行号,默认为已经处理的所有换行符个数加一。可以通过#line修改。
_ FILE_:当前处理的源字符串编号。
_ VERSION_:当前使用Opengl着色语言的整数表现形式。

预处理当中的条件分支:
根据宏定义以及整型常数的条件来判断进入不同的分支。包括不同的代码段。

//方式1
#ifdef NUM_ELEMENTS

#endif

//方式2
#if defined(NUM_ELEMENTS)&&NUM_ELEMENTS>3

#elseif NUM_ELEMENTS<7

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

编译器控制:
#pragma命令可以向编译器传递附加信息,并且在着色器代码编译的时候设置一些额外属性。具体例子在下:

//编译器优化选项
//启用或者禁止用着色器优化,会直接影响该命令所在的着色器源代码。
#pragma optimize(on)

#pragma optimize(off)
//必须在函数定义的代码块之外设置,一般默认的着色器都开启了优化
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//编译器调试选项
//启用或者禁用着色器的额外诊断信息输出。
#pragma debug(on)

#pragma debug(off)
//这些选项只在函数定义的代码块之外进行设置。
//默认下,所有着色器都会禁用调试选项。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

全局着色器编译选项:
除了之前使用的

#pragma STDGL 
  • 1

还有着色器扩充功能处理
GLSL预处理器提供了#extension命令。用于提示着色器的编译器在编译时如何处理可用的扩展内容。

#extension extension_name:<directive>
//此处的extension_name和调用 glGetString(GL_EXTENSIONS)时获取的扩展功能名称是一致的。或者也可以使用:
#extension all:<directive>
  • 1
  • 2
  • 3

directive可用的选项有:
require:如果无法支持给定的扩展功能,或者被设置为all,那么会提示错误。
enable:如果无法支持给定的扩展功能,则给出警告。如果设置为all,那么提示错误。
warn:如果无法支持给定的扩展功能,或者在编译的过程中使用了任何扩展,则给出警告。
disable:禁止支持给定的扩展,或者如果设置为all,那么禁止所有的扩展支持,之后当代码中涉及到这个扩展使用时,提示警告或者错误。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/107853
推荐阅读
相关标签
  

闽ICP备14008679号