Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • OpenGL简介与安装配置
    • 概念
      • 状态机
      • 对象
      • 绘制一个球体的流程
    • 创建窗口:使用GLFW
    • API加载器:GLAD
    • 数学库:glm
    • 样板代码与项目结构
      • 第一个OpenGL程序
      • 项目结构参考
  • OpenGL绘制图形
  • OpenGL中的缩放旋转与平移
  • 《OpenGL》学习笔记
Sirius0v0
2023-08-24
目录

OpenGL简介与安装配置

# OpenGL简介与安装配置

OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。实际的OpenGL库的开发者通常是显卡的生产商,购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。

# 概念

# 状态机

OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。

假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。

当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。

# 对象

在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct):

struct object_name {
    float  option1;
    int    option2;
    char[] name;
};

# 绘制一个球体的流程

在计算机图形学中,只需要表示物体的表面即可,对于计算机,任一个物体都可以用无数个三角形逼近拼凑而成,根据三角形的多少可以分为高模和低模。

我们可以利用一系列坐标表示一个物体:顶点坐标+顶点之间的连接关系。

渲染又可以分为实时渲染和离线渲染,离线渲染可以得到“照片级”渲染,但是计算成本极大。为了高效实时渲染,通常使用光栅化的方法,比路径追踪的射线与三角形求交高效得多。

为了追求真实感、立体感,需要进行着色(shade),在相应的像素点填充符合光学规律的颜色,区分亮暗,使之看起来立体。

GPU渲染一个物体的全部流程

由于图形渲染这一道道工序就像流水线,故得名渲染管线(rendering pipeline)。

# 创建窗口:使用GLFW

在我们作画之前,首先要做的就是创建一个OpenGL上下文和一个用于显示的窗口。然而,这些操作在每个系统上都是不一样的,我们手动处理创建窗口,定义OpenGL上下文以及处理用户输入是相当麻烦的。

幸运的是,有一些库已经提供了我们所需的功能,其中一部分是特别针对OpenGL的。我将使用GLFW,GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入,对我们来说这就够了。

进入GLFW的github仓库 (opens new window),下载到本地,使用如下命令进行添加链接即可。

add_subdirectory(glfw)
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)

# API加载器:GLAD

古代OpenGL往往使用系统自带的头文件:

#include <GL/gl.h>

在现代OpenGL中不再使用上述头文件,取而代之的是使用GLAD加载器:

#include <glad/glad.h>

glad实际上是一个python包,他所做的是根据你指定的版本,生成加载OpenGL全部函数的glad.c和glad.h头文件。例如:

pip install glad
python -m glad --out-path . --generator c --api gl=4.6 --profile compatibility

也可以通过在线生成器 (opens new window)进行配置。

同时,你可以添加CMakeLists.txt使其能够生成为一个静态库。

当使用了glad,就不要再导入gl.h了。

此外,glad头文件必须放在其他gl相关库的前面

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

建议自定义一个头文件写这两行,用的时候添加该头文件即可。

# 数学库:glm

安装即可。

# 样板代码与项目结构

以上为环境配置的所有内容,想要测试环境是否成功搭建,可以参考下文,复制相关代码并组织为一个样板代码,进行环境测试。

# 第一个OpenGL程序

画图之前,需要初始化GLFW库

if(!glfwInit()) {
    throw std::runtime_error("Failed to initialize GLFW");
}

然后使用GLFW创建一个窗口,之后把刚刚创建的窗口设为接下来gl函数的上下文。

GLFWwindow* window = glfwCreateWindow(640, 480, "Hello OpenGL", NULL, NULL);
if(!window) {
  glfwTerminate();
  throw std::runtime_error("GLFW failed to create window");
}
glfwMakeContextCurrent(window);

有了上下文之后,就可以初始化GLAD库了。注意,必须先gladLoadGL后才能使用gl函数,不先加载的话,gl函数就还是空指针,调用则会崩溃。

if(!gladLoadGL()) {
  glfwTerminate();
  throw std::runtime_error("GLAD failed to load GL functions");
}
std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << '\n';

在开始画图之前还有一件重要的事情要做,必须告诉OpenGL渲染窗口的尺寸大小,即视口(Viewport),这样OpenGL才只能知道怎样根据窗口大小显示数据和坐标。我们可以通过调用glViewport函数来设置窗口的维度

glViewport(0, 0, 640, 480);

此时此刻!我们终于可以画图了,只有将画图函数置于不断循环的语句中,图形才会长期存在,可以检测窗口是否被关闭作为循环是否跳出条件:

while (!glfwWindowShouldClose(window)) {
  // 清除缓存
  glClear(GL_COLOR_BUFFER_BIT);
  // 将画图函数封装
  render();
  // 双缓冲提交
  glfwSwapBuffers(window);
  // 拉取最新事件
  glfwPollEvents();
}

需要注意的是,glfwWindowShouldClose判断属于一个事件,没有主动获取最新事件的话,依然无法判断事件的发生,所以需要通过glfwPollEvents获取最新事件,这样才能让glfwWindowShouldClose生效。

当然,写好的图形绘制代码需要提交才能被驱动应用并绘制出来,利用glfwSwapBuffers()提交。

当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。我们可以在main函数的最后调用glfwTerminate函数来完成。

glfwTerminate();
return 0;

可以使用函数glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);注册窗口大小正在变化时所做的处理,例如重新设置视口、更新宽高比等。

可以在循环加入对按键的检测,实现按下ESC关闭窗口:

if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
  glfwSetWindowShouldClose(window, true);

# 项目结构参考

我使用了vcpkg包管理器来管理glm库,同时下载glfw, glad源码作为项目的模块,项目结构如下:

.
├── CMakeLists.txt
├── cmake
│   └── MyUsefulFuncs.cmake
├── glad
│   ├── CMakeLists.txt
│   ├── include
│   │   ├── KHR
│   │   │   └── khrplatform.h
│   │   └── glad
│   │       └── glad.h
│   └── src
│       └── glad.c
├── glfw
│   ├── CMake
│   ├── CMakeLists.txt
│   ├── deps
│   │   ├── ...
│   ├── include
│   │   └── GLFW
│   │       ├── glfw3.h
│   │       └── glfw3native.h
│   └── src
│       └── ...
├── include
└── src
    └── main.cpp

根目录下的CMakeLists.txt配置如下,仅供参考:

cmake_minimum_required(VERSION 3.18)

if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 添加 include/find_package 搜索 xxx.cmake 文件目录
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake;${CMAKE_MODULE_PATH}")
# 设置 vcpkg.cmake 目录
set(CMAKE_TOOLCHAIN_FILE "$ENV{MY_VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")

project(opengl_demo LANGUAGES CXX C)

if (PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR)
    message(FATAL_ERROR "The binary directory of CMake cannot be the same as source directory! "
        "Do not use 'cmake .'! Please consider use 'cmake -B build' to specify a different binary directory. "
        "Otherwise this project may fail to build, or make Git hard to exclude binary files. "
        "For Windows, it is highly recommended to use Visual Studio as IDE for CMake projects.")
endif()

message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")

if (MSVC)  # 1. fix string literial cannot include unicode characters; 2. rid min and max macros; 3. get back M_PI, M_E macros
    add_compile_options($<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/utf-8$<SEMICOLON>/DNOMINMAX$<SEMICOLON>/D_USE_MATH_DEFINES>)
    add_compile_options($<$<COMPILE_LANG_AND_ID:C,MSVC>:/utf-8$<SEMICOLON>/DNOMINMAX$<SEMICOLON>/D_USE_MATH_DEFINES>)
elseif (CMAKE_COMPILER_IS_GNUCC)  # Add necessary checks and warnings
    if (CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]")
        add_compile_options($<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wall$<SEMICOLON>-Wextra>)
        add_compile_options($<$<COMPILE_LANG_AND_ID:CXX,GNU>:-ftrapv$<SEMICOLON>-fbounds-check$<SEMICOLON>-fsanitize-trap>)
        # add_compile_options($<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fsanitize=address$<SEMICOLON>-fsanitize=leak>)
        # link_libraries(asan)
    endif()
endif()

file(GLOB_RECURSE sources CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE headers CONFIGURE_DEPENDS include/*.h include/*.hpp)
add_executable(${PROJECT_NAME} ${sources} ${headers})
target_include_directories(${PROJECT_NAME} PUBLIC include)

find_package(glm CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC glm::glm)
add_subdirectory(glfw)
target_link_libraries(${PROJECT_NAME} PUBLIC glfw)
add_subdirectory(glad)
target_link_libraries(${PROJECT_NAME} PUBLIC glad)

target_compile_definitions(${PROJECT_NAME} PUBLIC -DOPENGLTUTOR_HOME=\"${CMAKE_CURRENT_SOURCE_DIR}/\")
编辑 (opens new window)
#OpenGL#Cpp
上次更新: 2023/08/26, 00:19:22
OpenGL绘制图形

OpenGL绘制图形→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2024 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式