一、鸟瞰视角
渲染器用于创建和操作GPU资源并发出渲染命令。图2.1展示了我们渲染器的主要组件。少量的渲染状态用于配置渲染管线的固定功能组件以进行渲染。鉴于我们使用的是完全基于着色器的设计,渲染状态并不多,只有深度和模板测试之类的内容。渲染状态不包括那些可在着色器中实现的传统固定功能状态,比如逐顶点光照和纹理环境。
-
着色器程序(Shader Program)描述顶点、几何、片段着色器。我们的渲染器还包含用于使用顶点属性和统一变量与着色器通信的类型。
-
顶点数组(Vertex Array)是一种轻量级容器对象,用于描述绘制时使用的顶点属性。它通过一个或多个顶点缓冲区接收这些属性的数据。可选的索引缓冲区对顶点缓冲区进行索引,以选择要绘制的顶点。
-
二维纹理(Texture2D)表示驱动程序控制内存中的图像,当与描述过滤和环绕行为的采样器结合使用时,着色器可访问这些图像。数据分别通过写入和读取像素缓冲区与纹理进行传输。像素缓冲区是传输数据的通道;最终,图像数据存储在纹理本身中。
-
帧缓冲(Framebuffer)是纹理的轻量级容器,支持渲染到纹理技术。帧缓冲可包含多个纹理作为颜色附件,以及一个纹理作为深度或深度/模板附件。
-
设备类(Device) 是创建渲染对象的静态工厂类(C++中用命名空间实现类似效果),不负责发出渲染命令,
Context
用于发出渲染命令。这种区别类似于Direct3D中的ID3D11Device
和ID3D11DeviceContext
。如图 2.2:
-
Dear ImGUI库负责创建代表用于绘制的画布的图形窗口,包含用于渲染、窗口调整大小和输入处理的上下文和事件。使用
Device
创建的对象可在多个上下文Context
中共享。例如,客户端代码可创建两个使用相同着色器、顶点/索引缓冲区、纹理等的不同 ImGUI 子窗口。本章将详细描述这些类型,但栅栏除外,栅栏将在后续章节连同OpenGL多线程一起描述。 -
Context类,如图2.3所示,除了发出渲染命令外,
Context
还用于创建某些渲染器对象并包含一些状态。与Device
创建的可在任何上下文中使用的对象不同,Context
创建的对象只能在该上下文中使用。只有两种对象属于这种不可共享的类别:顶点数组和帧缓冲区,二者都是轻量级容器。
参考提示:接口设计想屏蔽实现细节(如底层 API )影响,但顶点数组、帧缓冲区因 OpenGL 限制无法跨上下文共享,且作为轻量级容器,让它们共享所产生的 OpenGL 开销相对自身会很大 。
二、搭建脚手架
1. 框架选型
- UI库:Dear ImGUI
- 窗口管理器:跨平台 GLFW
- 图像处理库: stb_image.h
- 数学库: glm
- 项目构建编译:CMake
2. 项目结构
.
├── .vscode/
├── assets/
├── bin/
├── build/
├── extern/
│ ├── glad/
│ ├── glfw/
│ ├── glm/
│ ├── imgui/
│ ├── KHR/
│ └── stb/
├── include/
│ ├── app/
│ │ └── Triangle.h
│ └── renderer/
│ ├── Context.h
│ └── Device.h
├── scripts/
│ ├── configure_debug.bat
│ ├── configure_release.bat
│ ├── debug_build.bat
│ └── release_build.bat
├── src/
│ ├── app/
│ │ └── Triangle.cpp
│ ├── renderer/
│ │ ├── glad.c
│ │ ├── Context.cpp
│ │ └── Device.cpp
│ └── main.cpp
├── .clang-format
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── imgu.ini
└── README.md
3. CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(CelestialEngine LANGUAGES CXX C)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
if(MSVC)
add_compile_options(
"$<$<CONFIG:Debug>:/Zi>" # generate debug information
"$<$<CONFIG:Debug>:/Od>" # disable optimization
)
#(GCC/Clang)options
else()
add_compile_options(
"$<$<CONFIG:Debug>:-g>"
"$<$<CONFIG:Debug>:-O0>"
)
endif()
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
endif(WIN32)
set(EXTERNAL_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern)
set(GLFW_ROOT ${EXTERNAL_LIB_DIR}/glfw)
set(IMGUI_ROOT ${EXTERNAL_LIB_DIR}/imgui)
set(GLM_ROOT ${EXTERNAL_LIB_DIR}/glm)
file(GLOB_RECURSE SOURCE_FILES
src/*.cpp
src/*.c
)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${GLM_ROOT}
${EXTERNAL_LIB_DIR}
)
find_library(GLFW_LIB glfw3
PATHS ${GLFW_ROOT}/lib
NO_DEFAULT_PATH
)
if(NOT GLFW_LIB)
message(FATAL_ERROR "GLFW library not found in: ${GLFW_ROOT}/lib")
endif()
add_library(stb_image INTERFACE)
target_include_directories(stb_image INTERFACE ${EXTERNAL_LIB_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE stb_image)
find_package(OpenGL REQUIRED)
add_library(imgui STATIC
${IMGUI_ROOT}/imgui.cpp
${IMGUI_ROOT}/imgui_demo.cpp
${IMGUI_ROOT}/imgui_draw.cpp
${IMGUI_ROOT}/imgui_tables.cpp
${IMGUI_ROOT}/imgui_widgets.cpp
${IMGUI_ROOT}/backends/imgui_impl_glfw.cpp
${IMGUI_ROOT}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(imgui PUBLIC
${IMGUI_ROOT}
${IMGUI_ROOT}/backends
${GLFW_ROOT}/include
)
target_link_libraries(imgui PRIVATE
${GLFW_LIB}
OpenGL::GL
)
target_link_libraries(${PROJECT_NAME} PRIVATE
imgui
${GLFW_LIB}
OpenGL::GL
)
if(MSVC)
target_link_options(${PROJECT_NAME} PRIVATE "/IGNORE:4099")
add_compile_options(/MD)
endif()
set(OUTPUT_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/assets
${OUTPUT_DIR}/assets
COMMENT "Copying assets to build directory..."
)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/imgui.ini
${OUTPUT_DIR}/imgui.ini
COMMENT "Copying imgui.ini to output directory"
)
4. Device.h
#pragma once
#include <memory>
#include "renderer/BufferHint.h"
#include "renderer/PixelBufferHint.h"
namespace Renderer {
class ShaderProgram;
class VertexBuffer;
class IndexBuffer;
class WritePixelBuffer;
class Texture2D;
class Texture2DDescription;
namespace Device {
// 创建着色器程序
std::shared_ptr<ShaderProgram> createShaderProgram(const std::string& vertexShaderSource,
const std::string& geometryShaderSource,
const std::string& fragmentShaderSource) {
// TODO
}
// 创建各种缓冲区
std::shared_ptr<VertexBuffer> createVertexBuffer(BufferHint usageHint, size_t sizeInBytes) {}
std::shared_ptr<IndexBuffer> createIndexBuffer(BufferHint usageHint, size_t sizeInBytes) {}
std::shared_ptr<WritePixelBuffer> createWritePixelBuffer(PixelBufferHint usageHint, int sizeInBytes) {}
// std::shared_ptr<Texture2D> createTexture2D(Texture2DDescription description) {}
// ...
} // namespace Device
} // namespace Renderer
5. Context.h
#pragma once
#include <memory>
namespace Renderer {
enum class ClearBuffers {
ColorBuffer = 1,
DepthBuffer = 2,
StencilBuffer = 4,
ColorAndDepthBuffer = ColorBuffer | DepthBuffer,
All = ColorBuffer | DepthBuffer | StencilBuffer
};
class VertexArray;
class Framebuffer;
class TextureUnits;
class Rectangle;
class ClearState;
class PrimitiveType;
class DrawState;
class SceneState;
struct Context {
virtual ~Context() = default;
virtual std::shared_ptr<VertexArray> createVertexArray() = 0;
virtual std::shared_ptr<Framebuffer> createFramebuffer() = 0;
virtual TextureUnits& getTextureUnits() = 0;
virtual Rectangle& getViewport() = 0;
virtual void setViewport(const Rectangle& viewport) = 0;
virtual const std::shared_ptr<Framebuffer>& getFramebuffer() const = 0;
virtual void setFramebuffer(const std::shared_ptr<Framebuffer>& framebuffer) = 0;
virtual void clear(const ClearState& clearState) = 0;
virtual void draw(PrimitiveType primitiveType,
int offset,
int count,
const DrawState& drawState,
const SceneState& sceneState) = 0;
// ...
};
} // namespace Renderer
参考:
- Cozi, Patrick; Ring, Kevin. 3D Engine Design for Virtual Globes. CRC Press, 2011.