基本介绍
- OpenGl是一个规范,他由显卡制造商负责提供实现
- 因此你使用的OpenGl出现了问题,升级对应的显卡应该就能解决
- OpenGl分为立即渲染模式【固定渲染管道】和核心模式
- 核心模式可以让程序对图形绘制过程拥有更多的控制,但同时也意味着更难,因为他抽象了更多的细节
- OpenGl的核心模式从3.3开始,剩下的版本都是在他的基础上扩展的,核心架构没有改变。
- 所以当我们要使用OpenGl的某些高级特性的时候,需要知道对应的显卡是否支持
扩展
- 当显卡公司提出某些新的特性的时候,会在驱动上以扩展的方式实现
- 我们只要在代码里面询问该显卡是否支持该扩展就可以直接使用该特性
- 而不必等待该特性发展成为OpenGl的特性一部分,一般来说,流行的扩展最终会变成扩展
状态机,对象
- OpenGl本身是一个巨大的状态机
- 我们可以创建多个设置对象,通过将不同的设置对象和上下文绑定,来进行不同的操作
GLFW
- 是一个提供给基础操作的库
- 在每一个操作系统上面,创建窗口和处理用户输入等操作都是不同的,OpenGl从这些细节抽象了出来
- 这意味着我们要自己处理这些操作,不过还好有一些c库可以直接使用比如GLFW
- 我们需要下载其源码,然后自己进行编译,这样的目的是为了保证和我们的cpu操作系统匹配
- 但是每个人用的IDE都是不同的,要根据源码构建对应的项目工程文件是一件很繁琐的事情
- 因此我们需要用到CMake
GLAD
- 是一个用来提供查询函数地址的库
- 因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。
- 由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。
- 所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。
- 取得地址的方法因平台而异,在Windows上会是类似这样:
// 定义函数原型
typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
// 找到正确的函数并赋值给函数指针
GL_GENBUFFERS glGenBuffers = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
// 现在函数可以被正常调用了
GLuint buffer;
glGenBuffers(1, &buffer);
- 有些库能简化此过程,其中GLAD是目前最新,也是最流行的库。
画一个窗口
- 首先对glfw进行初始化,设定gl版本和使用范围
- 然后创建窗口window
- 再对glad进行初始化,因为接下来要使用opengl的函数
- 设置视口大小
- 设置渲染循环,在渲染循环里面接收输入,交换渲染缓冲等
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(80, 600, "jiayajie", NULL, NULL);
if (window==NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
//将窗口设置为该线程上下文
glfwMakeContextCurrent(window);
//对glad进行初始化
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "failed to initialize GLAD" << std::endl;
return -1;
}
//定义视口大小 不必和glfw相同
glViewport(0, 0, 10, 10);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//检测glfw是否被要求退出 如果退出就直接返回false
while (!glfwWindowShouldClose(window))
{
//处理输入
processInput(window);
//渲染指令
glClearColor(0.2, 1, 1, 1); //状态设置
glClear(GL_COLOR_BUFFER_BIT); //使用指定颜色清空屏幕 使用状态
glfwSwapBuffers(window); //交换颜色缓冲
glfwPollEvents(); //检测输入输出事件
}
glfwTerminate();
return 0;
}
//设置窗口改变时回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow *window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
图形渲染
- 图元装配 将顶点渲染成指定的图元形状点,线还是面
- 几何着色器 它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状
- 光栅化这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)
- 在片段着色器运行之前会进行裁切,丢弃超出视图像素
- 片段着色器则是生成屏幕对应像素的颜色
- 最后一个阶段则是测试和混合,进行深度测试,该像素能否被看到,如果看不到则抛弃。进行透明度测试,
VBO与VAO
- VBO是顶点缓冲对象,vertex buffer object,而VAO是顶点数组对象,vertex array object
- VAO更类似于VBO的升级版本,他负责记录VBO的各种配置,比如glEnableVertexAttribArray和glDisableVertexAttribArray的调用
- 通过glVertexAttribPointer 设置的顶点属性配置
- 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象
- 所以当我们绘制物体的时候只要绑定对应的VAO对象即可,绘制完毕再解绑VAO
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
总结
- 重新复习了一遍OpenGl的知识,分为以下部分
- Glfw处理输入以及创建窗口
- Glad负责查找对应驱动函数的位置
- VBOBuffer是顶点数据缓冲,内容是顶点的各种属性,包括位置,法线,颜色,贴图坐标
- VAO负责记录VBO的各种指针和状态
- 当VBO里面很多位置都重合的时候可以使用索引缓冲对象,将必要的顶点定义出来,然后用其在数组中的索引来定义顶点。这样就不用重复定义顶点
- 在渲染一个对象的时候,顶点数据由VAO负责,纹理数据则是放置在对应纹理单元中【记得将shader 材质与纹理对应】,其后是Shader程序使用这些数据渲染出来
lightingShader.setInt("material.diffuse", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);
- 最后使用glDrawArrays的时候就会将对应顶点数据三个为一组画成三角形
glDrawArrays(GL_TRIANGLES, 0, 36);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
碎片知识
- 通过uniform数据来在代码里面动态的改变shader程序里面的数据
- 通过开启深度测试,可以出现距离感,否则每一帧都会完全覆盖上一帧的图片
变换
- 我们可以通过矩阵对一个顶点进行变换
- 位移,旋转,缩放
- 因此通过构建一系列的矩阵,然后在顶点着色器里面对每个顶点进行相应变换,就可以得到对应效果
- 矩阵一般分为三个,model矩阵对模型自身进行变换
- view 矩阵将图像变换到以某个观察位置为原点的观察空间中
- projection矩阵对该观察空间中的矩阵进行投影变换,进行裁剪,将范围以外的物体丢掉,具有透视感
- 最后得到的就是二维坐标了
- 因此摄像机本质上就是一个view矩阵,我们移动的时候是相对改变view矩阵的参数
- 需要注意法线
光照
- 光源类型分为三种,平行光, 聚光灯, 点光源
- 平行光只有方向,聚光灯是一个圆锥,有一个角度,和照射距离,点光源则只有一个照射距离。
- 对于聚光灯和点光源而言,都需要有一个光照衰减函数。平行光不需要
- 光照对于物体的影响可以分为三个部分,环境光,高光,物体自身颜色。将每个灯光对于该物体影响叠加后输出就是该物体最终的颜色
- 然后就是各部分光照的计算方法了