OpenGL 学习记录 - 基础篇
编写: 王宇
2017-08-25
教程地址:
https://learnopengl-cn.github.io/
OpenGL
- Khronos组织制定并维护的规范
- 立即渲染模式: OpenGL3.2 开始废弃
- 核心模式(Core-profile)
- 扩展
- 状态机(State Machine)
- 对象
创建窗口
- 创建OpenGL上下文(Context),目前流行的库:GLUT、SDL、SFML、GLFW
- 构建GLFW
- 我们的第一个工程
- 链接:
- Windows上OpenGL: opengl32.lib已经包含在Microsoft SDK里了
- Linux上的OpenGL: 在Linux下你需要链接libGL.so库文件,这需要添加-lGL到你的链接器设置中
-lGLEW -lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi
- GLAD 配置
打开GLAD的在线服务,将语言(Language)设置为C/C++,在API选项中,选择3.3以上的OpenGL(gl)版本(我们的教程中将使用3.3版本,但更新的版本也能正常工作)。之后将模式(Profile)设置为Core,并且保证生成加载器(Generate a loader)的选项是选中的。现在可以先(暂时)忽略拓展(Extensions)中的内容。都选择完之后,点击生成(Generate)按钮来生成库文件。
GLAD现在应该提供给你了一个zip压缩文件,包含两个头文件目录,和一个glad.c文件。将两个头文件目录(glad和KHR)复制到你的Include文件夹中(或者增加一个额外的项目指向这些目录),并添加glad.c文件到你的工程中。
地址: http://glad.dav1d.de/
你好,窗口
你好,三角形
- 对象
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
- OpenGL的图形渲染管线-Graphics Pipeline
大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程
- 第一部分把你的3D坐标转换为2D坐标
- 第二部分是把2D坐标转变为实际的有颜色的像素。
-
着色器
图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
-
图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。
-
- 顶点着色器 - Vertex Shader
把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
- 图元装配 - Primitive Assembly
将顶点着色器输出的所有顶点作为输入,并所有的点装配成指定图元的形状
- 几何着色器 - Geometry Shader
把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
- 光栅化阶段 - Rasterization Stage
它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
- 片段着色器
主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
- Alpha测试和混合(Blending)阶段
这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
-
顶点输入
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
-
- 顶点缓冲对象 (Vertex Buffer Object)
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
unsigned int VBO;
glGenBuffers(1, &VBO);
- glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBindBuffer(GL_ARRAY_BUFFER, VBO);
- glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
-
GLSL顶点着色器的源代码:
- 第一行版本声明
- in 关键字,表述输入
- layout (location = 0) : 在存储中的起始位置
- 向量: 方向 + 大小
最多4个分量: vec.x vec.y vec.z vec.w
- 预定义变量gl_Position
会成为着色器的输出
-
编译着色器
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
#version 330 core
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
-
- 编译片段着色器的过程与顶点着色器类似,只不过我们使用GL_FRAGMENT_SHADER常量作为着色器类型
-
着色器程序
- 着色器程序对象(Shader Program Object)
是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
-
创建一个程序对象
-
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
...
}
glUseProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
-
-
第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
- 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
- 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
- 下个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
- 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
-
最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
-
启用顶点属性: glEnableVertexAttribArray
- 小结:
顶点属性默认是禁用的。自此,所有东西都已经设置好了:我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。在OpenGL中绘制一个物体,代码会像是这样:
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
unsigned int VAO;
glGenVertexArrays(1, &VAO);
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
float vertices[] = {
// 第一个三角形
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, 0.5f, 0.0f, // 左上角
// 第二个三角形
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
float vertices[] = {
0.5f, 0.5f, 0.0f, // 右上角
0.5f, -0.5f, 0.0f, // 右下角
-0.5f, -0.5f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f // 左上角
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
-
-
VAO 保存 EBO
- 小结
// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
- 大小: 58.2 KB
- 大小: 28.5 KB
- 大小: 9.8 KB
- 大小: 27.9 KB
- 大小: 14.1 KB
- 大小: 35.3 KB
- 大小: 13.4 KB
分享到:
相关推荐
在本篇“Android OpenGL 学习笔记(一)”中,我们将探讨如何在Android平台上使用OpenGL ES进行图形渲染。OpenGL ES是OpenGL的一个轻量级版本,专为嵌入式系统设计,包括移动设备如智能手机和平板电脑。这篇笔记将...
这篇学习笔记1主要会探讨基础的OpenGL设置和渲染原理。 在OpenGL编程中,我们首先需要设置上下文环境,这通常通过窗口系统接口(如GLUT或SDL)完成。在"base.cpp"这个文件中,我们可能看到初始化OpenGL窗口的代码,...
本篇教程是基于Qt4框架下使用C++语言进行GUI开发的版本,主要介绍如何利用Qt提供的OpenGL支持来创建和操作OpenGL窗口。 #### 二、基础知识预备 - **Qt编程基础**:本教程假定读者已经具备一定的Qt编程经验,至少...
本篇笔记将深入探讨Cocos2D-X中精灵类的使用及其相关知识点。 首先,我们来了解什么是精灵。在游戏开发中,精灵通常指的是包含单一图像或者动画序列的图形对象。Cocos2D-X中的`cc::Sprite`类就是这样的一个对象,它...
在配合的文章《OpenGL学习笔记——JNI篇》中,作者通过JNI(Java Native Interface)来演示如何在Java程序中调用OpenGL进行图形绘制。JNI是Java平台的一部分,允许Java代码和其他语言写的代码进行交互,这在需要高...
在“OpenGL_P7”这个文件中,可能包含的是作者在学习过程中的代码示例或者项目实践,比如阶段性的实验代码、笔记文档或教程资源。读者可以通过研究这些材料来进一步加深对OpenGL ES的理解。 总之,OpenGL ES的学习...
本篇Shader学习笔记主要介绍了Shader的基础概念、Hello World示例、Uniform关键字以及用算法进行绘画的方法。 1. Shader基础: - **什么是Shader**:Shader是运行在GPU上的小程序,用于处理图形渲染,随着分辨率...
这篇教程和学习笔记将帮助你深入理解CEGUI的原理和使用方法。 首先,CEGUI的核心设计理念是组件化。这意味着它包含了一系列可复用的UI元素,如按钮、文本框、滚动条等,这些元素都可以单独配置和定制。在HTML和Word...
VTK,全称为 Visualization Toolkit,是一款强大的开源可视化库,尤其在3D图形渲染和科学数据可视化方面具有广泛的应用。...希望这篇“初学者的VTK学习笔记”能对你有所帮助,祝你在VTK的学习旅程上取得成功!
本篇学习笔记主要涵盖了ArcGIS for Android的基础配置和核心组件MapVie的使用。 首先,配置ArcGIS for Android项目需要在`Project`级别的`build.gradle`文件中添加Esri的仓库,确保能获取到所需的库。接着,在`...
这篇文档提供了关于红模仿的小作坊博客导航栏v2.0.0版本的详细信息,由作者红模仿-红胖子维护,并提供了博客链接供用户查询。文档内容涉及多个技术专栏,重点介绍了Qt开发、树莓派、三维、OpenCV、OpenGL、ffmpeg、...
这篇学习笔记将深入探讨这两个概念及其在实际应用中的使用。 首先,Canvas可以理解为画布,它是Android系统提供的用于在屏幕上绘制图形的对象。在Android中,我们可以通过Canvas来绘制各种形状,如线条、矩形、圆、...
本篇笔记将聚焦于 Direct3D 的学习,尤其是 Direct3D 的初始化过程。 Direct3D 初始化的核心是创建 Device 对象,这是进行图形渲染的基础。在给定的代码中,开发者通过调用 `new Device()` 来创建 Device 实例。...
这篇“WorldWind学习笔记[二]worldwind 在applet上部署”旨在帮助开发者理解如何在Web浏览器中利用Applet技术运行WorldWind应用程序。 首先,我们来看一下必要的库文件。`worldwind.jar`是WorldWind的主要库,包含...
通过这个基础框架,开发者可以逐步学习和实践Three.js的各种特性,如材质、纹理、动画、交互等,从而构建出丰富的3D交互应用。Three.js社区活跃,提供了大量的示例、教程和插件,对于初学者和有经验的开发者都是极好...
本篇将围绕"安卓Android源码——libgdx-0.9.6.zip"进行深度解析,帮助开发者理解和掌握LibGDX的核心特性与使用技巧。 首先,我们来看"AUTHORS"、"CONTRIBUTORS"这两个文件,它们记录了LibGDX项目的主要作者和贡献者...
2012-06-11 21:41 0 OpenGL编程基础 源码.zip 2012-06-11 21:26 55,505 PHP实现多服务器共享SESSION数据.docx 2012-06-11 21:40 49,392 Pointers on C.zip 2012-06-11 21:22 3,386,253 RTOS_MDK uCOS-II for STM32...
这篇学习笔记将深入探讨NDK开发的基本概念、流程以及如何利用C++进行编程。 首先,我们来了解一下NDK的核心作用。NDK为开发者提供了在Android平台上编译和运行原生代码的能力,这对于需要高效计算或直接操作硬件...
本篇将详细探讨一个基于Android平台的微信打灰机应用的源码,旨在帮助有志于Android开发的同学们深入理解Android应用的构建过程,以及如何进行毕业设计和论文撰写。 首先,我们要明确“打灰机”在编程语境下通常是...
《C++实现连连看游戏源代码解析》 连连看,这款经典的休闲益智游戏,以其简单易懂的规则和挑战性的玩法深受玩家喜爱。本文将深入探讨如何利用C++编程语言,结合DIRECTX...希望这篇解析能对你的学习和实践带来启发。