欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL

程序员文章站 2023-12-25 20:27:57
...

OpenGl

OpenGL

OpenGl管线

GLSL着色器

代码片段

// 声明GLSL的版本为3.3
#version 330 core
// 传入参数
in vec4 vPosition;
in vec4 vColor;
// 传出数据
out vec4 color;
// 程序控制输入数据
uniform mat4 ModelViewProjectionMaxtrix;

void 
main()
{
    color = vColor;        // 颜色不变
    gl_Position = ModelViewProjectionMaxtrix * vPosition; // 位置根据传入矩阵进行修改
    // 无需返回值
}
/*
    多行注释写法
*/

基础数据类型

基本数据类型

类型 描述
float 32位浮点值
double 64位浮点值
int 有符号二进制补码的32位整数
uint 无符号32位整数
bool 布尔值
  • 所有变量必须在声明的同时进行初始化
int i, num = 1500;
float force, g= -9.5;
bool falling = true;
double pi = 3.1415926535;
  • 作用域

    1. 几乎跟C语言一样
    2. 在任何函数之外定义的是全局变量
    3. 在一组大括号之内定义的,只在打括号内有效
    4. for(int i=0; i<cout; i++) i只在这个for循环中有效
  • 隐式转换

    • GLSL的隐式转换比C语言少,int f = flase 会报错
    • GLSL更注重类型安全
目标类型 可以隐式转换到目标类型
uint int
float int, uint
double int, uint, float

聚合类型

矩阵

基本类型 2D向量 3D向量 4D向量 矩阵类型
float vec2 vec3 vec4 mat2 mat3 mat4
mat2x2 mat2x3 mat2x4
mat3x2 mat3x3 mat3x4
mat4x2 mat4x3 mat4x4
double dvec2 dvec3 dvec4 dmat2 dmat3 dmat4
dmat2x2 dmat2x3 dmat2x4
dmat3x2 dmat3x3 dmat3x4
dmat4x2 dmat4x3 dmat4x4
int ivec2 ivec3 ivec4 ——————
uint uvec2 uvec3 uvec4 ——————
bool bvec2 bvec3 bvec4 ——————
  • 构建聚合类型
vec3 vel = vec3(0.0, 2.0, 3.0);
ivec3 step = ivec3(vel);

vec4 color;
vec3 RGB = vec3(color);     // 只包含color的前四个分量

vec3 whtie = vec3(1.0f);    // white = (1.0, 1.0, 1.0)
vec4 trans = vec4(whtie, 0.5);  // trans = (1.0, 1.0, 1.0, 0.5)

mat3 m = mat3(4.0);     // 3x3的矩阵中的主对角线值为4.0,其他位置值为0
mat3 m = mat3(1.0, 2.0, 3.0,
              4.0, 5.0, 6.0,
              7.0, 8.0, 9.0);
vec3 row1 = vec3(1.0, 2.0, 3.0);
vec3 row2 = vec3(4.0, 5.0, 6.0);
vec3 row3 = vec3(7.0, 8.0, 9.0);
mat3 m = mat3(row1, row2, row3);
  • 访问聚合类型
分量访问符 符号描述
(x,y,z,w) 与位置相关的分量
(r,g,b,a) 与颜色相关的分量
(s,t,p,d) 与纹理坐标相关的分量
vec3 lum = color.rrr; 
color = color.abgr; // 反转color的每个分量

// 一条语句中,只能使用一种类型的访问符
vec4 color = otherColor.rgz;      // 错误 z不是(rgba)访问符中的
// 访问元素不要超过变量类型范围
vec2 pos;
float oPos = pos.z;               // 错误 z超过2D向量(x,y)

结构体

struct Particle{
    float lifeTime;
    vec3 position;
    vec3 vecl;
};
Particle p = Particle(1.0, vec3(1.0, 2.0, 3.0), vec3(1.0, 2.0, 3.0));

数组

  • GLSL 4.3 之后可以数组套数组,之前不可
  • 使用 [ ] 来进行索引
  • 不允许使用负数索引或者超出范围的索引
  • 数组具有构造函数
float coff[3];      // 3个float元素的数组
float[3] coff;      // 同上
int intdic[];       // 未定义维数,可以后面重新声明

float coff[3] = float[3](2.22, 3.14, 4.2);  // 通过数组的构造函数创建数组
int len = coff.length();                    // 获得数组长度

mat3x4 m;
m.length(); // 列数为3
m[0].length();  // 行数为4

float digitl[m.length()];       // 设置数组大小与矩阵列数相同
float digitl[gl_in.length()];   // 设置数组大小与几何着色器的输入定点数相同

存储限制符

类型修饰符 描述
const 将一个变量定义为只读形式。如果它初始化时使用的是一个编译时的常量,那么它本身会称为编译时的常量
in 设置这个变量为着色器阶段的输入变量
out 设置这个变量为着色器阶段的输出变量
uniform 设置这个变量为用户应用程序传递给着色器的数据,它对于给定的图元而言是一个常量
buffer 设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用
shared 设置变量是本地工作组(local work group)*享的。它之恩那个用于计算着色器中
  • const 存储限制符
  • in 存储限制符:可以是顶点属性(对于顶点着色器),或者是前一个着色器阶段的输出变量
  • out 存储限制符:定义着色器阶段的输出变量,例如着色器中输出变换后的其次坐标,或者片元着色器中输出的最终片元颜色
  • buffer 存储限制符:需要在应用程序*享一大块缓存给着色器时使用buffer,着色器对缓存可以读和写
  • shared 存储限制符:只能用于计算着色器中,可以建立本地工作组共享的内存
  • uniform 存储限制符:是应用程序设置好的,在图元处理中不会发生变化,所有可用的着色阶段共享,必须定义为全局变量

uniform使用

应用程序在着色器运行之前可以,可以设置着色器中uniform变量的值,且该变量在着色器计算中不可更改。

uniform vec4 BaseColor; // 着色器中定义

着色器中可以直接通过BaseColor变量名来使用,但是在应用程序中不能直接通过变量名来设置它的值。
GLSL编译器会在链接着色器程序时创建一个uniform列表,在OpenGL中通过GLint glGetUniformLocation(GLuint program, const char* name)函数来获得uniform列表

GLint glGetUniformLocation(GLuint program, const char* name)
// name为着色器中uniform变量名,name是以NULL结尾的字符串,不存在空格
// 如果name与启用的着色器中的素有uniform变量民都不想符或者与内部保留的着色器变量名相同返回-1
void glUniform****(GLint location, ...) // 以glUniform开头的函数 来设置unifrom的值
  • 样例代码
GLint timeLoc;      // 着色器中uniform变量time的索引
GLfloat timeValue;  // 程序运行时间
ShaderInfo shaders[] = {
    {GL_VERTEX_SHADER, "triangles.vert"},	// 顶点着色器
    {GL_FRAGMENT_SHADER, "triangles.frag"}, // 片段着色器
    {GL_NONE, NULL}
};
GLuint program = LoadShaders(shaders);
glUseProgram(program);
timeLoc = glGetUniformLocation(program, "time");
glUniformlf(timeLoc, timeValue);

语句

算数运算符

优先级 操作符 可用类型 描述
1 ( ) —— 成组操作
2 [ ]
f()
.(句点)
++ –
数组、矩阵、向量
函数
结构体
算数类型
数组的下标
函数调用与构造函数
访问结构体的域变量或者方法
后置递增/递减
3 ++ –
+ -
~
~
算数类型
算数类型
整型
布尔型
前置递增/缔结
一元正/负
一元按位 非
一元逻辑非
4 * / % 算术类型 乘法运算
5 + - 算数类型 相加运算
6 << >> 整型 按位操作
7 <> <= >= 算数操作 关系比较操作
8 == != 任意 相等操作
9 & 整型 按位与
10 ^ 整型 按位异或
11 | 整型 按位或
12 && 布尔型 逻辑与
13 ^^ 布尔型 逻辑异或
14 || 布尔型 逻辑或
15 a?b:c 布尔型?任意:任意 三元操作符
16 =
+= -=
*= /=
%= <<= >>=
&= |= ^=
任意
算数类型
算数类型
整型
整型
赋值
算数赋值
17 ,(逗号) 任意 操作符序列

操作符重载

  • GLSL中大部分操作符都是经过重载的
  • 如果需要进行向量和矩阵之间的乘法(操作数和顺序非常重要,必须符合规则)
    • 基本限制条件是要求矩阵和向量的维度必须是匹配的
    • 两个向量相乘得到的是一个逐分量相乘的新向量(即按位相乘,而不会生成矩阵)
    • 两个矩阵相乘得到的是通常矩阵相乘的结果(按照矩阵乘法相乘)

流控制

  • if-else
  • switch
if(truth){

} else{

}

switch(int_value){
    case b:
    break;

    case d:
    break;

    default:
    break;
}

循环

  • for
  • while
  • do…while
for(int i=0; i<10; i++>){

}
while(n<10){

}
do{

}while(n < 10);

流控制语句

  • break
  • continue
  • return
  • discard:丢弃当前的片元,终止着色器的执行。discard语句只能用于片元着色器中。

函数

returnType functionName([accessModifier] type1 variable1, [accessModifier] type1 variable12, ...)
{
    // 函数体
    return returnValue;
}

参数限制符

访问修饰符 描述
in 将数据拷贝到函数中(如果没有指定修饰符,默认该形式)
const in 将只读数据拷贝到函数中
out 从函数中获取数值(因此输入函数的值是未定义的)
inout 将数据拷贝到函数中,并且返回函数中修改的数据
  • 在函数中写入一个in类型的变量,相当于对变量的局部拷贝进行了修改,只在函数自身范围内产生作用

计算的不确定性

  • GLSL无法保证在不同的着色器中,两个完全相同的计算会得到完全一样的结果。这一问题与CPU对应用程序进行计算时的问题相同,即不同的优化方式可能会导致结果非常细微的差异。这细微的差异对于多通道的算法会产生问题,因为各个着色器阶段可能需要计算得到完全一致的结果。
  • GLSL保证着色器之间的计算不变性:invariantprecise关键字
  • 这两个关键字都需要在图形设备上完成计算过程,来确保同一表达式的结果可以保证重复(不变)性
  • 但是对于宿主计算机和图形硬件各自的计算,这两种方法都无法保证结果的完全一致性
uniform float ten;          // 假设应用程序设置该值为10.0
const float f = sin(10.0);  // 宿主机的编译器负责计算
float g = sin(ten);         // 图形硬件负责计算
void main(){
    if(f == g);         // 无法保证该值相等
}

invariant

invariant gl_Position;                  // 内置的输出变量
invariant centroid out vec3 Color;      // 自定义输出变量

invariant 可以设置任何着色器的输出变量。它可以确保如果两个着色器的输出变量使用了同样的表达式,并且表达式中的变量也是相同值,那么产生的结果也是相同的。
输出变量的作用是将一个着色器的数据从一个阶段传递到下一个。可以在着色器中用到某个变量或者内置变量之前的任何位置,对该变量设置关键字invariant

// 如果需要将着色器中的所有可变量都设置为invariant
#param STDGL invariant(all)

precise

precise可以设置任何计算中的变量或者函数的返回值。它的作用不是增加数据精度而是增加计算的可复用性。

这段还没弄懂 后面写代码注意一下

数据块接口

着色器和应用程序之间,着色器各个阶段之间共享的变量可以组织为变量快的形式,并且有时候必须采用这种形式。uniform变量可以使用uniform块,输入和输出变量可以使用in和out块,着色器的存储缓存可以使用buffer块

// uniform块的写法
uniform b{      // 限定访问符可以是uniform in out 或者 buffer
    vec4 v1;    // 块中的变量列表
    bool v2;
};             // 访问块成员时使用v1,v2

uniform b{
    vec4 v1;
    bool v2;
}name;          // 访问块成员时使用name.v1 name.v2

上述代码中,块开始的部分(代码中为 b)对应于外部访问时的接口名称,结尾部分的名称(代码中为name)用于在着色器代码中访问具体成员变量
如果着色器程序变得复杂,那么用到unifrom变量的数量会上升,通常多个着色器程序会用到同一个uniform变量。
由于uniform变量的索引位置是在glLinkProgram()的时候产生的,所以应用程序获得的索引有可能发生变化。
uniform缓存对象就是优化对uniform变量的访问,以及在不同着色器程序之间共享uniform数据的方法

着色器编译

字符串1
着色器源代码1
着色器对象1
着色器程序
可执行的着色器程序
字符串2
着色器源代码2
着色器对象2
...
  • 为什么创建多个着色器对象
    • 可能在不同的程序中复用同一个GLSL程序
  1. 调用glCreateShader来创建着色器对象
  2. 将着色器的源代码关联到这个对象上,使用glShaderSource
  3. 如果要编译着色器对象的源代码,使用glCompileShader
    1. 如果编译错误,可以通过调取编译日志来判断错误的原因
    2. glGetShaderInfoLog函数会返回一个具体实现相关问题的信息
  4. 当创建并编译了所有必要的着色器对象之后,创建可执行程序,glCreateProgram(创建空的着色器程序)
  5. 将空的着色器程序关联到必要的着色器对象上glAttachShader
  6. 如果要从程序中移除一个着色器对象glDetachShader
  7. 当所有必要的着色器惯量到着色器程序之后,可以链接对象来生成可执行程序glLinkProgram
    1. 由于着色器对象中可能存在问题,因此链接过程可能失败
    2. 调用glGetProgramiv来查询连接操作的结果,如果返回GL_TRUE则链接成功,GL_FALSE链接失败
    3. 如果链接失败,调用glGetProgramInfoLog来获取程序链接的日志信息
  8. 如果链接成功,可以使用glUseProgram来启用顶点或者片元程序
  9. 当着色器任务完成之后,可以通过glDeleteShader来删除
  10. 当不再需要着色器程序之后调用glDeleteProgram来删除着色器程序
  11. 最后,调用glIsShader来判断某个着色器对象是否存在,或者调用glIsProgram来判断着色器程序是否存在

OpenGL绘制方式

图元

  • OpenGL支持很多不同的图元类型
    • 点,线,或者三角形(可以再组合为条带、循环体、扇面等,大多数设备都支持的基础图元类型)
    • 作为细分器输入的Patch类型
    • 作为几何着色器输入的临街图元

  • 通过单一的顶点来表示,一个点就是一个四维的其次坐标值,因此点不存在面积

  • OpenGl通过显示屏幕上的一个四边形区域来模拟点

  • 渲染点图元的时候,OpenGL会通过一系列光栅化规则来判断点所覆盖的像素位置

  • 设置点的大小

    • 默认的点大小为1.0
    • glPointSize(GLfloat size)
    • 可以在顶点、细分、几何着色器中设置gl_PointSize来设置

线、条带与循环

  • OpenGL中的线指的是线段,独立的线用一对顶点来表达,每个顶点为线段端点

    • 多线段链接首位闭合的叫做 循环线
    • 开放的多线段叫做 条带线
  • 设置线段的宽度

    • void glLineWidth(GLfloat width)
    • width表示线的宽度,必须是一个大于0.0的值

三角形、条带和扇面

  • 三角形:独立的三角形
  • 条带:相邻三角形共享一条边
  • 扇面:第一个点作为共享点存在

两个三角形的共享边上的像素值因为同时被两者所覆盖,因此不可能不收到光照计算的影响
两个三角形的共享边上的像素值,不可能受到多于一个三角形的光照计算影响
上面两句话总结就是:OpenGL对于模型三角形共享边的光栅化过程不会产生任何裂缝,也不会产生重复的绘制

OpenGL图元的模式标识

图元模型 OpenGL枚举量
GL_POINTS
线 GL_LINES
条带线 GL_LINESTRIP
循环线 GL_LINE_LOOP
独立三角形 GL_TRIANGLES
三角形条带 GL_TRIANGLE_STRIP
三角形扇面 GL_TRIANGLE_FAN

将多变形渲染为点集、轮廓线、实体

  • 一个多变形有 正面和背面,不同的面朝向观察者时可能会有不同的渲染结果
  • 默认情况下正面和背面的绘制方法是一样的,如果要修改这个属性需要设置glPolygonMode
void glPolygonMode(GLenum face, Glenum mode);
// face 必须是 GL_FRONT_ANDBACK
// mode 可以是 GL_POINT、GL_LINE、GL_FILL
//  绘制模式是 点集、轮廓线、填充模式

多变形的反转和裁剪

  • 一般来说,多变形的顶点在屏幕上应该是逆时针方向排列的(法线向上,右手比划一下大拇指向上时其余手指弯曲方向是逆时针)

    • 对于球体、环形体、茶壶等都是有向的
    • 对于莫比乌斯环、克林瓶不是逆时针
  • 如果我们要描述一个有向的模型表面,但是它的外侧需要使用逆时针方向进行描述,使用glFrontFace来反转

void glFrontFace(GLenum mode);
// 控制多变形正面的判断方式
//    默认模式是GL_CCW:多变形投影到窗口坐标系之后,顶点按照逆时针排列的面作为正面
//        模式是GL_CW: 采取顺时针方向的面将被认为是正面
  • 对于一个由不透明的且方向一致的多变形组成、完全封闭的模型表面来看说,它的所有背面多变形都是不可见的——会背正面多边形遮挡
    • 如果位于模型外侧,开启裁剪可来直接抛弃OpenGL中的背面多变形
    • 如果位于模型内存,需要指示OpenGL自动抛弃正面或者背面的多变形
void glCullFace(Glenum mode);
// 在转换到屏幕空间渲染之前,设置需要抛弃(裁剪)哪一类多变形
// mode 可以是:GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK
//    分别表示:正面、背面或者所有多变形
void glEnable() 
// GL_CULL_FACE参数来开启裁剪
void glDisable()
// 同样的 GL_CULL_FACE来关闭

上一篇:

下一篇: