运行环境:Linux 、C++

  • ​​本教程仅个人学习总结​​
  • ​​第一个hello world,渲染一个窗体​​
  • ​​渲染一个矩形​​

本教程仅个人学习总结

一切参考资源:都在官网。

1、安装glfw

首先下载glfw : ​​点击这里​

1、下载
2、解压
3、mkdir build && cd build
4、cmake … && make -j4
5、cd …
6、将此目录下的include目录里面的东西全拷到自己工程的include中
7、cd build/src 这个目录下面的静态库(默认编译是静态库) libglfw3.a ,为我们需要的库

1、 GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入,对我们来说这就够了。

因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用

2、安装GLAD
GLAD是一个开源的库,它能解决我们上面提到的那个繁琐的问题。GLAD的配置与大多数的开源库有些许的不同,GLAD使用了一个在线服务。在这里我们能够告诉GLAD需要定义的OpenGL版本,并且根据这个版本加载所有相关的OpenGL函数。
打开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文件到你的工程中。

3、OpenGL安装。本站一大堆教程这里就不多说了。

第一个hello world,渲染一个窗体

上文中说到GLFW提供了一系列操作OPENGL的接口,首先第一步即为初始化GLFW环境

#include "glfw3.h"

初始化前我们需要先知道GLFW的版本。

int major = 0, minor = 0, rev = 0;
glfwGetVersion(&major, &minor, &rev); //查看GLFW版本号
std::cout << "GLFW version: " << major << "." << minor << "." << rev << std::endl;

然后我们在初始化GLFW环境

if (glfwInit() == GLFW_FALSE)
{
std::cout << "Failed to initialize GLFW" << std::endl;
}
std::cout << "Success to initialize GLFW" << std::endl;

glfwInit这个函数有如下需要注意的点:

PS1:如果此函数失败,它会在返回前调用glfwTerminate。如果成功,您应该在应用程序退出之前调用glfwTerminate。
PS2:此函数只能从主线程调用。

我们还需要告诉GLFW我们使用的openGL版本和模式

//教程都是基于OpenGL 3.3版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); //主板本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); //次
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //核心模式

具体设置GLFW的约束条件,我也没整明白~,目前就这么用。1、版本号,2、使用的openGL模式。

接下来就是创建窗口

GLFWwindow *window = glfwCreateWindow(800, 600, "HelloWorld", NULL, NULL);//倒数第二个参数是选择的监视器模式
//最后一个参数,基本用不到。
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}

注意创建失败的话,需要清除所有窗体。glfwTerminate此函数会销毁所有剩余的窗口和光标

接下来我们要设置窗体的归属,简单来说就是设置窗体为那个线程锁持有。

glfwMakeContextCurrent(window);

一个窗体,大小肯定是可变的。我们可以通过鼠标去拉伸和缩放,这样窗体的大小就是可变的。因此我们要设置一个回调函数
实时设置我们手动改变窗体的大小。

void set_render_size(GLFWwindow *window, int width, int height);

glfwSetFramebufferSizeCallback(window, set_render_size); //注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数

void set_render_size(GLFWwindow *window, int width, int height)
{
std::cout<< width<<" "<< height<<std::endl;
glViewport(0, 0, width, height);
}

下面就是要我们使用openGL来渲染这个窗体,这就要用到GLAD

再次说明一下GLAD的作用便于理解。

到这里还没有结束,我们仍然还有一件事要做。因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。

初始化GLAD库:常用接口如下:
int gladLoadGLLoader(GLADloadproc load):任何的OpenGL接口调用都必须在初始化GLAD库后才可以正常访问。如果成功的话,该接口将返回GL_TRUE,否则就会返回GL_FALSE。
其中GLADloadproc函数声明如下:
void* (*GLADloadproc)(const char* name)


GLFWglproc glfwGetProcAddress ( const char * procname )
如果当前上下文支持,此函数返回指定 OpenGL 或 OpenGL ES核心或扩展函数的地址。
上下文必须在调用线程上是当前的。在没有当前上下文的情况下调用此函数将导致GLFW_NO_CURRENT_CONTEXT错误。
//初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}

const GLubyte *OpenGLVersion = glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现的版本号:%s\n", OpenGLVersion);
我们不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。
因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),
它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的渲染循环。
while (!glfwWindowShouldClose(window)) // glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,
{ //如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
processInput(window);

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glfwSwapBuffers(window); //函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
glfwPollEvents(); //函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
}

最后销毁所有窗体

//销毁所有窗口
glfwTerminate();

这样我们渲染一个窗体的大体流程就是这些

完整源码如下

#include <iostream>
#include <cstdio>
#include <sys/unistd.h>

#include "glad.h"
#include "glfw3.h"

void set_render_size(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);
int main()
{
int major = 0, minor = 0, rev = 0;
glfwGetVersion(&major, &minor, &rev); //查看GLFW版本号
std::cout << "GLFW version: " << major << "." << minor << "." << rev << std::endl;
if (glfwInit() == GLFW_FALSE)
{
std::cout << "Failed to initialize GLFW" << std::endl;
}
std::cout << "Success to initialize GLFW" << std::endl;

//教程都是基于OpenGL 3.3版本展开讨论的,所以我们需要告诉GLFW我们要使用的OpenGL版本是3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); //主板本号
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); //次
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //核心模式

GLFWwindow *window = glfwCreateWindow(800, 600, "HelloWorld", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}

glfwMakeContextCurrent(window); //使调用线程的指定窗口的上下文成为当前的
glfwSetFramebufferSizeCallback(window, set_render_size); //注册这个函数,告诉GLFW我们希望每当窗口调整大小的时候调用这个函数:
//初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}

const GLubyte *OpenGLVersion = glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现的版本号:%s\n", OpenGLVersion);

/*
不希望只绘制一个图像之后我们的应用程序就立即退出并关闭窗口。
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。
因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),
它能在我们让GLFW退出前一直保持运行。下面几行的代码就实现了一个简单的渲染循环:
*/
while (!glfwWindowShouldClose(window)) // glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,
{ //如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
processInput(window);

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glfwSwapBuffers(window); //函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
glfwPollEvents(); //函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
}
//销毁所有窗口
glfwTerminate();
return 0;
}

void set_render_size(GLFWwindow *window, int width, int height)
{
std::cout<< width<<" "<< height<<std::endl;
glViewport(0, 0, width, height);
}

void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}

运行结果如下

OpenGL教程一,窗体和图形的渲染(包含OpenGL、GLFW、GLAD)_openGL

渲染一个矩形

接下来我们尝试在渲染窗体中加上图形的渲染,理解起来可能有点抽象。

首先假设渲染图形肯定不可能只是平面图像吧,只是在屏幕上显示的是2D的图像。比如说一个苹果,在现实世界中肯定是3D的存在,但是我们屏幕
只是一个2D的平面,我们如何将一个现实世界3D的物体,转换成屏幕上2D显示呢(2D显示3D的效果)?这就涉及到坐标的转换了。

3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管
道,期间经过各种变化处理最终出现在屏幕的过程)管理的。

图形渲染管线可以被划分为两个主要部分:
1、第一部分把你的3D坐标转换为2D坐标
2、2D坐标转变为实际的有颜色的像素。

图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。
.
有些着色器可以由开发者配置,因为允许用自己写的着色器来代替默认的,所以能够更细致地控制图形渲染管线中的特定部分了。因为它们运行在GPU上,所以节省了宝贵的CPU时间。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading > Language, GLSL)写成的,在下一节中我们再花更多时间研究它。
.
下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。

​要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。​

OpenGL教程一,窗体和图形的渲染(包含OpenGL、GLFW、GLAD)_c++_02


如上图即是绘制渲染一个图片的流程。按照官网的教程说的话,,,,,,,有点过于抽象繁琐,简单来说就是如下几个过程。1、你想渲染一个苹果,首先你得知道这个苹果在坐标系中的位置坐标吧。

OpenGL教程一,窗体和图形的渲染(包含OpenGL、GLFW、GLAD)_openGL_03


那么​​顶点着色器​​它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。

再粗暴点理解就一句话​​顶点着色器 : 坐标转换用的​

需要注意

2、​​图元装配​​阶段将就是将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状

简单点来说​​将顶点着色器输出的点,绘制出对应的形状​

3、​​几何着色器​​这玩意是干啥的捏???上个图

OpenGL教程一,窗体和图形的渲染(包含OpenGL、GLFW、GLAD)_openGL_04


​几何着色器​​其实就是决定线怎么连,最终绘制成什么样子的图形!这样说还无法理解,那对着上图悟吧!!!!!!1

4、​​光栅化阶段​​,这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

简单点理解就是,将​​几何着色器​​生成的形状,这块形状包含的像素块圈起来。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

5、​​片段着色器​​的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

这部分作用一句话概括就是:确认像素的颜色。

6、​​Alpha测试和混合(Blending)阶段​​ 这最后一步是干啥的呢?????就是。。。。。。上色!

接下来我们在窗体中渲染一个矩形
首先GLFW、GLAD的初始化是不可少的,除去循环渲染的部分,其他部分和上一小节完全是一样的,我们直接copy即可。

#include <glad.h>
#include <glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;


int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
...


//todo


...
glfwTerminate();
return 0;
}

void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}

接下来我们要开始渲染一个矩形了

首先我们现创建顶点着色器。

// 创建顶点着色器 
//glCreateShader 创建着色器 参数是着色器类型:GL_VERTEX_SHADER是顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);

注意一下上文有这样一句话

有些着色器可以由开发者配置,因为允许用自己写的着色器来代替默认的,所以能够更细致地控制图形渲染管线中的特定部分了。因为它们运行在GPU上,所以节省了宝贵的CPU时间。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的

我们创建了顶点着色器,接下来我们就要编写着色器的程序。

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

// 创建顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
//替换着色器中的代码:顶点
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
//编译已存储在由 指定的着色器对象中的源代码字符串
glCompileShader(vertexShader);

后续明日更完!