您当前的位置: 首页 >  ar

Jave.Lin

暂无认证

  • 5浏览

    0关注

    704博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

LearnGL - 02 - DrawTriangle - VBO/Shader

Jave.Lin 发布时间:2020-06-05 21:42:26 ,浏览量:5

文章目录
  • VAO,VBO,EBO/IBO 几个重要的数据对象
  • 顶点变换处理
  • 顶点缓存创建、绑定、设置数据
  • 着色器
    • 顶点着色器
    • 片元着色器
    • 使用着色器
      • 取得编写的 GLSL 着色器脚本
        • 顶点着色器
        • 片元着色器
      • 创建着色器程序
      • 创建着色器子程序,设置好子程序类型,设置好对应脚本
      • 编译着色器子程序
        • 编译失败诊断日志
      • 将子程序附加到着色器程序
      • 将着色器附加到程序
      • 将程序链接
        • 链接失败诊断日志
      • 设置当前管线使用的程序
  • 设置顶点属性格式
  • 开始绘制
    • 视口大小
    • 清理缓存
    • 绘制
    • 绘制效果
      • 完整代码
  • References

LearnGL - 学习笔记目录

本人才疏学浅,如果什么错误,望不吝指出。

VAO,VBO,EBO/IBO 几个重要的数据对象

在OpenGL中,我们可以看到很多相关的博客,或是开源项目中都可以看到,VAO,VBO,EBO/IBO,到底是什么鬼。

这几个玩意都是一些英文的缩写:

  • VAO : Vertex Array Object,顶点数组对象
  • VBO : Vertex Buffer Object, 顶点缓存对象
  • EBO/IBO : Element/Index Buffer Object, 索引缓存对象

这篇只是用 VBO,以后学习使用到 VAO、EBO/IBO 再说吧:

  • VBO 出了本篇,还有下一篇:
    • LearnGL - 02.1 - DrawTriangle_Extension - VBO/Shader
  • EBO/IBO 的文章:
    • LearnGL - 03 - DrawQuad - VBO/EBO
  • VAO 相关:
    • LearnGL - 04 - VAO 探究
    • LearnGL - 04.1 - DrawDoubleQuad - UsingVAO

在学习 使用 OpenGL 绘制内容前,最好线了解基本的 OpenGL 渲染管线,References 文章 你好,三角形 也有讲到。要想更详细讲解,大家可以搜索了解:图形渲染管线 或是 光栅化图形渲染管线。或是查看之前的一篇 LearnGL - 01.1 - OpenGL 概述 & 管线概述

在现代的GPU设计中,必须要有一个VS(Vertex Shader,顶点着色器)和一个FS(Fragment Shader,片段着色器,也叫片元着色器),所以在我们绘制一个三角形时,我们需要编写最简单的VS和FS。

绘制三角形之前,我们需要有三角形的三个点的坐标

float vertices[] = {
	// x,y,z
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

渲染管线可以简单的分为三大阶段:

  • 应用程序阶段 : 主要负责几何、光栅化阶段需要的渲染数据,顶点、索引、着色器、还有一些绘制状态配置。
  • 几何阶段 : 主要对应用程序阶段输入的顶点变换处理。
  • 光栅化阶段 : 主要对几何阶段处理后的点作插值生成片段后的处理。

其中几何阶段中,比较重要的是,顶点的变换过程。

顶点变换处理

我们输入的都是3D坐标,而最终显示的是在2D的平面屏幕上的。

所以需要将:3D坐标变换为2D屏幕坐标:OpenGL Transformation 几何变换的顺序概要(MVP,NDC,Window坐标变换过程)

变换顺序:Object Space -> World Space -> View Space -> Projection Space -> NDC Space -> Screen Space

其中 Projection Space -> NDC Space -> Screen Space 底层硬件会处理。

了解的基本的变换过程后,为了简单起见,我们尝试上面三个坐标点都当做NDC坐标点来使用,后续在对 Object Space -> World Space -> View Space 做一些介绍。

上面三个坐标点的z值为0,在OpenGL的NDC中,x,y,z在[-1~1]之间都是可视的NDC范围内的坐标,超出NDC Space的坐标点都当做是看不见的。 (Drirect的x,y都是[-1~1],但是z是[0~1]之间的,这点与OpenGL的NDC是不一样的)

在这里插入图片描述

所以上面三个点是可以看到的。

基本上在NDC下这三点组成的三角形如下: 在这里插入图片描述

顶点缓存创建、绑定、设置数据

使用顶点缓存对象,需要使用 glGenBuffers 来创建缓存对象。如下:

GLuint vertex_buffer;
glGenBuffers(1, &vertex_buffer);

生成缓存的vertex_buffer就相当于在显卡的内存(显存)中生成了一个空指针。这也 vertex_buffer 也就是我们的之前说的 VBO。

这是 vertex_buffer 都是默认的数据,直到调用:glBindBuffer 后才是我们自己想要的数据,而绑定的缓存类型要设置好,这里我们绑定到:GL_ARRAY_BUFFER 的类型缓存。

glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);

绑定后,后续对GL_ARRAY_BUFFER 类型的缓存对象都会对 vertex_buffer 数据操作。

下面可以是用 glBufferData 来对上面我们绑定的 GL_ARRAY_BUFFER 的缓存对象设置(将 CPU 内存 数据复制到 GPU 显存)数据,即:对GPU显存中的 vertex_buffer 指针设置数据。

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBufferData 的 uage 参数我们使用的是 GL_STATIC_DRAW,因为缓存数据几乎不改变,这样性能大大提升。

现在有顶点数据了,也创建了顶点缓存对象了,也设置了顶顶啊缓存对象的数据。

那么就可以开始配置好 至少需要的两个着色器:顶点着色器 和 片段着色器 就可以开始绘制了。

着色器

想要详细的了解 GLSL 4.5 版本的 可以查看 OpenGL Shading Language Specification 4.5

OpenGL 管线中的各个着色器的指定的阶段、位置。

在 OpenGL 着色器的使用可以简单的分为几个步骤:

  • 取得编写的 GLSL 着色器脚本
  • 创建着色器程序
  • 创建着色器子程序,设置好子程序类型,设置好对应脚本
  • 编译着色器子程序
  • 将着色器附加到程序
  • 将程序链接
  • 设置当前管线使用的程序

着色器语言,GLSL 也就是OpenGL Shading Language,是一门 CLike 语言(与C语言很类似的)。

GLSL是在OpenGL 2.0 发布的(也就说之前的版本是没有的,只能用固定管线)。

OpenGL 兼容模式(compatibility profile)还是可以使用固定功能的管线(fixed-function pipeline)的,在OpenGL 核心模式(core profile)是没有固定功能管线的。 这里使用的是核心模式的。 其实后面我改用 compatibility profile 了,因为使用的API比较久的话,对应 core profile 下的话,会有过时API导致无法正常绘制。因为我参考的学习资料是3.3的API资料。

顶点着色器

下面我们列出一个最简单的顶点着色器:

#version 450 core
layout (location = 0) in vec3 vPos;
void main() {
    gl_Position = vec4(vPos, 1.0);
}

第一行中的 #version 450 core ,就是指定使用的版本,指定这个Shader 是运行在 450 版本的 GLSL,这个 450 其实与我们的 OpenGL 4.5 是对应的,450 == 4.5 * 100

core 对应我们之前说的OpenGL 核心模式。(后面我改用 compatibility profile 了)

但一般我们会按照这个着色器使用的API特性来决定它的 version 的,想我们上面这个这么简单的着色器,使用的都是非常基础的特性,可以用 110 版本就可以了。这样它就可以兼容在比较低版本的 OpenGL 或是GLSL比较低的版本中运行。但这里图方便,我直接写了个 450 版本的。

第二行 layout (location = 0) in vec3 pos;,分几个部分来讲解吧。

  • layout(location = 0) : 是布局限定符(layout qualifier),location是指定这类shader变量类型属性所处的索引值。它可以用于uniformattribute。布局限定符不是必须的,对于 attribute 的话,如果布局限定符不手动指定location索引值的话,GLSL 编译时会自动分配对应的索引值,可以使用对应的 API来获取。对于 uniform 的话,是在着色器程序链接时确定的,后面的正文有讲到。
  • in : 表示这时着色器的输入数据,在这个顶点着色器中会将应用程序设置的数据复制到该变量上,也可以查看下面的GLSL 的类型修饰符。
  • vec3 : 包含3个分量的浮点型向量类型,可以通过(x,y,z或是r,g,b来访问,更详细的分量访问可以查看下表GLSL 向量访问分量符,也可以去搜索了解:swizzle)
  • vPos: 这是变量的名称,这里用"v"开头的用意是指:在顶点着色器中的变量。(后面我们将片元着色器fragment shader时,是以"f"开头作,当然这不是必须的,都是个人喜欢的命名方式,怎么样可以让代码可读性高一些就好。)

GLSL 的类型修饰符

类型修饰符描述const将一个变量定义为只读形式。如果它初始化时用的是一个编译时常量,那么它本身也会成为编译时常量in设置这个变量为着色器阶段的输入变量out设置这个变量为着色器阶段的输出变量uniform设置这个变量为用于应用程序传递给着色器的数据,它对于给定的图元而言是一个常量buffer设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用shared设置变量是本地工作组(local work group)中共享的。它只能用于计算着色器中

上面对 uniform 特别讲解一下:uniform是对该所有着色器阶段都是共享使用的,它必须定义为全局变量。

GLSL 向量访问分量符

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

第三行 void main() { 就是顶点着色器的入口函数,不需要参数,也不需要返回值。

第四行 gl_Position = vec4(vPos, 1.0);gl_Position 是顶点着色器的内置输出变量,这个变量的值会传递到下一个阶段的着色器作为输入数据使用。这里我们将应用程序阶段设置的ndc space下的顶点数据直接作为gl_Position 来输出,只不过,输出时,我们不全了 vec4的分量,最后一个分量为1(作为齐次坐标)。可以这句代码也可以写成:gl_Position = vec4(vPos.x, vPos.y, vPos.z, 1.0);,这是swizzle语法的特点,就这一句的话,可以还很多种写法。

然后我们将这段 顶点着色器 的源代码设置到我们的一个变量中。

你也可以放在一个文件中,如:xxx.vert 文件中,但这里为了方便DEMO内聚的可读性,我就先放到一个变量中。

如下:

static const char* vertex_shader_text =
"#version 450 compatibility\n"
"attribute vec3 vPos;\n"
"void main() {\n"
"    gl_Position = vec4(vPos, 1.0);\n"
"}\n";

之前说过,顶点着色器 和 片元着色器 都是必须的要的。

那么下面我简单的来段 片元着色器 的介绍。

片元着色器
static const char* fragment_shader_text =
"#version 450 compatibility\n"
"void main() {\n"
"    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";

可以看到这段 片元着色器 的脚本比 顶点着色器 的还要简短。

第一行 和顶点着色器一样,都是脚本使用的GLSL 版本450,这点基本GLSL中所有着色器都是需要的。 第二行 片元着色器的main函数,也基本是着色器需要的。 第三行 对GLSL内置输出变量为红色值:gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);,这里的vec4(1.0, 0.0, 0.0, 1.0); 的 vec4 类型向量的分量可以解读为RGBA,R=1.0,G和B都是=0.0,A是1.0,所以可以理解为就是一个不透明的红色。

使用着色器

顶点着色器 和 片元着色器 的脚本都有了,那么就可以应用程序代码中使用它们,还记得之前将的使用步骤吧:(那么下面再列一次)

  • 取得编写的 GLSL 着色器脚本
  • 创建着色器程序
  • 创建着色器子程序,设置好子程序类型,设置好对应脚本
  • 编译着色器子程序
  • 将着色器附加到程序
  • 将程序链接
  • 设置当前管线使用的程序
取得编写的 GLSL 着色器脚本

再之前的两个 shader 脚本 复制过来,如下:

顶点着色器
static const char* vertex_shader_text =
"#version 450 compatibility\n"
"attribute vec3 vPos;\n"
"void main() {\n"
"    gl_Position = vec4(vPos, 1.0);\n"
"}\n";
片元着色器
static const char* fragment_shader_text =
"#version 450 compatibility\n"
"void main() {\n"
"    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";
创建着色器程序

用到 glCreateShader API

unsigned int vertex_shader;
vertex_shader = glCreateShader(GL_VERTEX_SHADER);

这样就创建了一个 顶点着色器 对象,ID为:vertex_shader

有了ID,需要给这个着色器指定源码

创建着色器子程序,设置好子程序类型,设置好对应脚本

用到 glShaderSource API

glShaderSource(vertex_shader, 1, &vertexShaderSource, NULL);
编译着色器子程序

用到 glCompileShader API

glCompileShader(vertex_shader);
编译失败诊断日志

也许编译不一定会成功,这时想要查看是什么原因导致编译失败,可以通过 glGetShaderiv,glGetShaderInfoLog 组合使用来查看编译日志。

// 获取编译状态值储存到:success 变量
GLint success, infoLogLen;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success) {
	glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &infoLogLen);
	GLchar* infoLog = (GLchar*)malloc(infoLogLen);				// 如果编译失败,则将编译日志储存到:infoLog 中
	glGetShaderInfoLog(vertex_shader, infoLogLen, NULL, infoLog);
	std::cout             
关注
打赏
1664331872
查看更多评论
0.1657s