SoftGLRender源码:视图管理器ViewManager
特性
文件:ViewManager.h
ViewManager
类管理着各种与显示有关的重要内容,它本身不产生任何显示资源,而是控制着这些资源. 主要包括:
- 配置数据(config_),交互的配置面板(configPanel_)
- 用于人机交互的轨道控制器(orbitController_)
- 3D模型加载器,场景资源(modelLoader_)
- 用于显示3D内容的视图(viewers_)
- 用于观察3D内容相机(camera_)
类图关系
Config
配置数据,负责存储程序的配置数据,包括默认配置、用户通过面板选择的数据ConfigPanel
配置面板,在UI上显示,负责与用户交互,选择的配置数据会保存到Config
对象ModelLoader
模型加载器,负责3D模型的加载Model
从3D文件加载的树形结构的3D模型DemoScene
场景,负责管理场景中所有模型,包括Model
,及其他类型Model(如直线模型ModelLine
,点模型PointModel
,天空盒模型SkyboxModel
,地面模型FloorModel
)OrbitController
3D场景的相机轨道控制器,负责将用户输入转化为对相机的控制Viewer
视图,负责显示3D内容,有OpenGL、软件、Vulkan三种方式实现. 一个ViewManager
对象,通常包含多个Viewer
Camera
相机,负责维护相机属性、透视投影观察体、MVP变换矩阵
ViewManager 数据成员
private:
void* window_ = nullptr; // GLFW窗口指针
int width_ = 0; // 窗口宽度
int height_ = 0; // 窗口高度
int outTexId_ = 0; // 整个窗口对应的纹理,main中创建的OpenGL纹理
std::shared_ptr<Config> config_; // 配置数据
std::shared_ptr<ConfigPanel> configPanel_; // 配置面板UI
std::shared_ptr<Camera> camera_; // 相机
std::shared_ptr<SmoothOrbitController> orbitController_; // 轨道控制器
std::shared_ptr<ModelLoader> modelLoader_; // 模型加载器
std::unordered_map<int, std::shared_ptr<Viewer>> viewers_; // 每个Viewer负责管理对应方案的整个渲染流程(Soft, OpenGL, Vulkan)
int rendererType_ = RENDER_TYPE_NONE; // 渲染类型:软件、OpenGL、Vulkan
bool showConfigPanel_ = true; // 显示配置面板开关
bool dumpFrame_ = false; // 转储帧开关
viewers_
有3个元素,分别代表了软件实现(Soft)、OpenGL实现、Vulkan实现的View. 通常,只需要使用其中1种方案.
ViewerManager 函数成员
构造与析构
在main函数中调用.
ViewManager
并没有定义构造与析构函数,但有延迟构造的create()
和回收资源的destroy()
.
create()
主要负责对相机、轨道控制器、配置数据、配置面板、视图创建(Soft、OpenGL、Vulkan 3种实现)、模型加载器等进行初始化.
注意:构造完Camera
对象后,还设置了透视投影参数. 因为Camera
并未定义构造函数,相机参数的初始化需要在setPerspective
中显式进行.
public:
// 可用于延迟构造, 由main函数调用
// @param window GLFW窗口指针
// @param width 窗口宽度
// @param height 窗口高度
// @param outTexId 输出纹理Id. 输出的图像,最终写到outTexId代表的纹理
bool create(void *window, int width, int height, int outTexId) {
window_ = window;
width_ = width;
height_ = height;
outTexId_ = outTexId;
// camera
camera_ = std::make_shared<Camera>();
camera_->setPerspective(glm::radians(CAMERA_FOV), (float) width / (float) height, CAMERA_NEAR, CAMERA_FAR);
// orbit controller
orbitController_ = std::make_shared<SmoothOrbitController>(std::make_shared<OrbitController>(*camera_));
// config
config_ = std::make_shared<Config>();
configPanel_ = std::make_shared<ConfigPanel>(*config_);
// viewer software
auto viewer_soft = std::make_shared<ViewerSoftware>(*config_, *camera_);
viewers_[Renderer_SOFT] = std::move(viewer_soft);
// viewer opengl
auto viewer_opengl = std::make_shared<ViewerOpenGL>(*config_, *camera_);
viewers_[Renderer_OPENGL] = std::move(viewer_opengl);
// viewer vulkan
auto viewer_vulkan = std::make_shared<ViewerVulkan>(*config_, *camera_);
viewers_[Renderer_Vulkan] = std::move(viewer_vulkan);
// model loader
modelLoader_ = std::make_shared<ModelLoader>(*config_);
// setup config panel actions callback
setupConfigPanelActions();
// init config
return configPanel_->init(window, width, height);
}
destroy()
负责重置状态、回收资源,包括:重置状态、释放每个视图(Viewer).
inline void destroy() {
resetStates(); // 重置状态
for (auto& it : viewers_) {
it.second->destroy(); // 销毁每个Viewer
}
}
配置面板回调
在create()
中被调用,负责为配置面板configPanel_
设置回调. 相当于初始化configPanel_
.
具体有哪些回调,取决于类ConfigPanel
有哪些部分需要监听.
因为ConfigPanel知道在何时发生与面板有关的事件,但不确定此时还需要做什么关联工作,因此ConfigPane提供回调让调用者决定.
void setupConfigPanelActions() {
// 重置相机时,重置轨道控制器
configPanel_->setResetCameraFunc([&]()->void {
orbitController_->reset();
});
// 重置Mipmap时,重置场景模型的状态
configPanel_->setResetMipmapFunc([&]()->void {
waitRenderIdle();
modelLoader_->getScene().model->resetStates();
});
// 重置reverse-Z时,重置各子view的reverse-Z
configPanel_->setResetReverseZFunc([&]()->void {
waitRenderIdle();
viewers_[config_->renderType]->resetReverseZ();
});
// 重载模型时,利用模型加载器modelLoader_重新加载模型
configPanel_->setReloadModelFunc([&](const std::string& path)->bool {
waitRenderIdle();
return modelLoader_->loadModel(path);
});
// 重置skybox模型时,利用modelLoader_重置skybox模型
configPanel_->setReloadSkyboxFunc([&](const std::string& path)->bool {
waitRenderIdle();
return modelLoader_->loadSkybox(path);
});
// 设置帧dump时,dump帧选项
configPanel_->setFrameDumpFunc([&]()->void {
dumpFrame_ = true;
});
// 更新光源时,同步更新场景点光源的属性
configPanel_->setUpdateLightFunc([&](glm::vec3& position, glm::vec3& color)->void {
auto& scene = modelLoader_->getScene();
scene.pointLight.vertexes[0].a_position = position; // 位置
scene.pointLight.UpdateVertexes(); // 同步GPU数据
scene.pointLight.material->baseColor = glm::vec4(color, 1.f); // 材质基础颜色
});
}
注意:Soft方案Viewer没有GPU数据,OpenGL方案Viewer才会用到GPU.
初始化配置面板
这部分涉及UI,到ConfigPanel
类再细讲.
主渲染循环
处理场景绘制和后期效果. 由main函数调用,每调用一次相当于生成一帧.
主要包括对轨道控制器、相机、配置面板、配置数据的更新. 根据不同的渲染方案(Soft, OpenGL, Vulkan),利用Viewer对象绘制帧.
int drawFrame() {
orbitController_->update();
camera_->update();
configPanel_->update();
// update triangle count
config_->triangleCount_ = modelLoader_->getModelPrimitiveCnt();
auto& viewer = viewers_[config_->renderType];
if (rendererType_ != config_->renderType) {
// change render type need to reset all model states
resetStates();
rendererType_ = config_->renderType;
viewer->create(width_, height_, outTexId_);
}
viewer->configRenderer();
if (dumpFrame_) {
RenderDebugger::startFrameCapture(viewer->getDevicePointer());
}
viewer->drawFrame(modelLoader_->getScene());
if (dumpFrame_) {
dumpFrame_ = false;
RenderDebugger::endFrameCapture(viewer->getDevicePointer());
}
return viewer->swapBuffer();
}
如果更新了渲染类型(Renderer_SOFT
, _OPENGL
, _Vulkan
),则需要重新创建并重置Viewer.
- 更新reverse-Z
对于Soft方案,根据配置(config_)更新相机(camera_)、 深度相机(cameraDepth_)的reverse-Z
// ViewSoftware.h
void configRenderer() override {
camera_->setReverseZ(config_.reverseZ);
cameraDepth_->setReverseZ(config_.reverseZ);
}
对于OpenGL方案,直接禁用reverseZ,然后同步到相机(camera_)和深度相机(cameraDepth_)
// ViewerOpenGL.h
void configRenderer() override {
// disabled
config_.reverseZ = false;
camera_->setReverseZ(config_.reverseZ);
cameraDepth_->setReverseZ(config_.reverseZ);
}
- dump frame
如果打开了帧dump开关,那么可利用render doc在帧绘制前后捕捉帧
...
if (dumpFrame_) {
RenderDebugger::startFrameCapture(viewer->getDevicePointer());
}
viewer->drawFrame(modelLoader_->getScene());
if (dumpFrame_) {
dumpFrame_ = false;
RenderDebugger::endFrameCapture(viewer->getDevicePointer());
}
...
捕获的帧,可以用图形调试工具RenderDoc分析. 包括:
- 查看API调用事件列表
- 查看流水线状态
- 查看纹理
- 查看网格
还能进行像素调试、着色器调试、API调用检查.
- 交换buffer
对于Soft方案,将软件渲染器生成的图像数据传输到OpenGL纹理中,最终返回纹理id.
// ViewerSoftware.h
int swapBuffer() override {
// 软件渲染器输出的纹理
auto* texOut = dynamic_cast<TextureSoft<RGBA> *>(texColorMain_.get());
// 纹理的底层像素缓冲区
auto buffer = texOut->getImage().getBuffer()->buffer;
GL_CHECK(glBindTexture(GL_TEXTURE_2D, outTexId_)); // 纹理对象(id)绑定到当前纹理单元
GL_CHECK(glTexSubImage2D(GL_TEXTURE_2D, // 纹理目标
0, // 多级渐远纹理级别(0为基本级别)
0, // 纹理X方向的偏移量
0, // 纹理Y方向的偏移量
(int)buffer->getWidth(), // 更新区域的宽度
(int)buffer->getHeight(), // 更新区域的高度
GL_RGBA, // 像素数据格式(如 GL_RGBA)
GL_UNSIGNED_BYTE, // 像素数据类型(如 GL_UNSIGNED_BYTE)
buffer->getRawDataPtr())); // 像素数据指针
return outTexId_;
}
对于OpenGL方案,主要处理多采样抗锯齿(MSAA)渲染结果的解析和传输,最终返回目标纹理id.
// ViewerOpenGL.h
int swapBuffer() override {
// texColorMain_ OpenGL渲染器输出的纹理
int width = texColorMain_->width;
int height = texColorMain_->height;
if (texColorMain_->multiSample) { // 检查是否启用MSAA
// Step1. 绑定MSAA(多采样)帧缓冲区
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, fbo_in_));
// 将MSAA 2D纹理附加到帧缓冲区FBO作为颜色附件
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE,
texColorMain_->getId(),
0));
// Step2. 绑定目标纹理
GL_CHECK(glBindTexture(GL_TEXTURE_2D, outTexId_));
GL_CHECK(glBindFramebuffer(GL_TEXTURE_2D, fbo_out_));
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outTexId_, 0));
// Step3. 执行多采样解析(Blit)
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_in_));
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_out_));
// 将MSAA帧缓冲区(fbo_in_)像素传输到单采样缓冲区(fbo_out_)
// GPU高效复制数据函数,无需CPU介入
GL_CHECK(glBlitFramebuffer(0, 0, width, height,
0, 0, width, height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST));
return outTexId_;
}
return texColorMain_->getId();
}
- srcX0, srcY0 源区域左下角(pixel)坐标
- srcX1, srcY1 源区域右上角坐标
- dstX0, dstY0 目标区域左下角坐标
- dstX1, dstY1 目标区域右上角坐标
- mask
GL_COLOR_BUFFER_BIT
颜色GL_DEPTH_BUFFER_BIT
深度GL_STENCIL_BUFFER_BIT
模板
- filter 过滤方式
GL_NEAREST
最接近纹理坐标的单个纹素(纹理像素),不做插值GL_LINEAR
双线性插值(Bilinear Interpolation)计算平滑的纹理采样结果
为什么这里要多次调用glBindFramebuffer,绑定缓冲区?
不同时候调用,有不同含义. 见注释Step1,2,3.
重置状态
在更改渲染类型、重新创建Viewer时,回收(destroy)渲染资源时,都调用resetStates()
进行了重置状态.
resetStates 主要做了什么,重置了哪些状态呢?
1)重置所有模型状态;
2)重置场景状态.
inline void resetStates() {
waitRenderIdle(); // 等待渲染器空闲
modelLoader_->resetAllModelStates(); // 重置所有模型状态
modelLoader_->getScene().resetStates(); // 重置场景状态
}
等待渲染器空闲
waitRenderIdle
根据不同Viewer类型(Soft、OpenGL、Vulkan),自动选择不同的等待渲染器空闲的方案.
inline void waitRenderIdle() {
if (rendererType_ != RENDER_TYPE_NONE) {
viewers_[rendererType_]->waitRenderIdle();
}
}
调用的都是基类的方法,即Viewer::waitRenderIdle()
,然后根据renderer_
所指实际对象,转发给子类的waitIdle()
.
好处:统一调用接口,运行时决定调用哪个方案.
void Viewer::waitRenderIdle() {
if (renderer_) {
renderer_->waitIdle();
}
}
对于Soft,什么也没做
void RendererSoft::waitIdle() { }
对于OpenGL,调用glFinish()
,提交GPU缓冲区指令,并等待指令完成.
// glFinish: 用于向图形硬件提交缓冲区里的指令,并等待所有指令执行完成后再返回
void RendererOpenGL::waitIdle() {
GL_CHECK(glFinish());
}
注意:glFinish
是一个显式阻塞(同步)函数,建议在需要严格同步时调用;与glFlush
区别是后者仅提交命令到GPU不等待完成,建议每帧结束时调用.
绘制面板UI
在main函数中调用,对应每一帧都调用.
根据配置面板开关showConfigPanel_
,实际绘制工作转发给ConfigPanel::onDraw
完成.
// 绘制面板
inline void drawPanel() {
if (showConfigPanel_) {// 如果启用显示配置面板
configPanel_->onDraw();
}
}
对应的控制开关,由togglePanelState()决定. 也是在main函数中调用.
inline void togglePanelState() {
showConfigPanel_ = !showConfigPanel_;
}
更新尺寸
因为配置面板与ImGui交互,需要根据尺寸决定显示窗口,因此需要同步更新尺寸.
在main.cpp的窗口尺寸改变回调framebufferSizeCallback
中调用.
// 更新窗口尺寸
inline void updateSize(int width, int height) {
width_ = width;
height_ = height;
configPanel_->updateSize(width, height);
}
捕获手势
捕获手势相关信息,人机交互输入信息,主要包括放缩、旋转、平移. 这部分信息存入轨道控制器,用于控制相机位置、朝向(旋转).
inline void updateGestureZoom(float x, float y) {
orbitController_->zoomX = x;
orbitController_->zoomY = y;
}
inline void updateGestureRotate(float x, float y) {
orbitController_->rotateX = x;
orbitController_->rotateY = y;
}
inline void updateGesturePan(float x, float y) {
orbitController_->panX = x;
orbitController_->panY = y;
}
放缩,在鼠标滚轮回调中捕获滚轮滚动信息
// Main.cpp
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) {
if (!viewer || viewer->wantCaptureMouse()) {
return;
}
viewer->updateGestureZoom((float)xOffset, (float)yOffset);
}
旋转和移动,在鼠标移动回调中捕获鼠标移动信息.
如果按下鼠标左键,则代表平移相机;如果未按下鼠标左键,则代表旋转相机.
// glfw: whenever the mouse moves, this callback is called
void mouseCallback(GLFWwindow* window, double xPos, double yPos) {
...
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
if (firstMouse) {
lastX = xPos;
lastY = yPos;
firstMouse = false;
}
double xOffset = xPos - lastX;
double yOffset = yPos - lastY;
if (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) {
viewer->updateGesturePan((float)xOffset, (float)yOffset);
}
else {
viewer->updateGestureRotate((float)xOffset, (float)yOffset);
}
lastX = xPos;
lastY = yPos;
}
else {
firstMouse = true;
}
}
查询是否正在捕获鼠标、键盘输入
需要利用ImGui框架捕获鼠标、键盘信息,如果ImGui正在使用鼠标、键盘,则不能处理,外部逻辑需要屏蔽输入.
inline bool wantCaptureKeyboard() {
return configPanel_->wantCaptureKeyboard();
}
inline bool wantCaptureMouse() {
return configPanel_->wantCaptureMouse();
}
基本概念
MSAA
多重采样抗锯齿(MSAA) 是在SSAA的基础上发展来的硬件抗锯齿技术. SSAA是理论上效果最好的抗锯齿方案.
什么是抗锯齿?
抗锯齿(anti-aliasing,AA) 也称边缘柔化、消除混叠、抗图像折叠有损,是减少游戏图形边缘的锯齿状像素格,使画面看上去更精细而不是满屏的马赛克.
SSAA核心思想:以4x为例,4xSSAA对于每个像素(Pixel)计算4个子像素,将4个子像素的颜色求平均值,便能获得抗锯齿后的颜色.
MSAA与SSAA区别:像素着色器(Pixel Shader)的运行次数. MSAA同样对于每个像素进行4次采样(Sample),但是只在像素中心位置运行一次像素着色,然后根据Sample是否被三角形覆盖而将像素着色的颜色复制到Sample上.
FBO
FBO(Framebuffer Object,帧缓冲区对象) 是 OpenGL 中用于离屏渲染(Off-screen Rendering)的核心技术,它允许将渲染结果输出到纹理或渲染缓冲区,而非直接显示到屏幕.
OpenGL函数
glBindTexture
glBindTexture
激活纹理对象. 成功后,所有后续纹理操作(如glTexImage2D
、glTexParameteri
)将作用于该纹理.
原型:
void glBindTexture(GLenum target, GLuint texture);
target
纹理类型,常用值:- GL_TEXTURE_2D(2D纹理)
- GL_TEXTURE_CUBE_MAP(立方体贴图)
- GL_TEXTURE_3D(3D纹理)
texture
纹理对象的ID(glGenTextures
生成),0表示解绑当前纹理.
使用流程:
- 创建并绑定纹理
GLuint textureID;
glGenTextures(1, &textureID); // 生成纹理ID
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定到2D纹理目标
// 设置纹理参数
// 双线性过滤: 在纹理坐标周围选取四个最近的纹素进行两次线性插值,最终生成平滑过渡的像素颜色
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载纹理数据
// 将内存中pixel数据data,按指定纹理格式写到GPU纹理中
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- 渲染时绑定纹理
glActiveTexture(GL_TEXTURE0); // 选择纹理单元0
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理
- 解绑纹理
glBindTexture(GL_TEXTURE_2D, 0); // 解绑当前纹理
可用GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
查询支持的纹理单元上限.
glTexSubImage2D
glTexSubImage2D
更新现有2D纹理的全部或部分数据,比glTexImage2D
更高效,适合动态修改纹理内容而无需重新分配存储空间.
原型:
void glTexSubImage2D(
GLenum target, // 纹理目标(如 GL_TEXTURE_2D)
GLint level, // 多级渐远纹理级别(0为基本级别)
GLint xoffset, // 纹理X方向的起始偏移量
GLint yoffset, // 纹理Y方向的起始偏移量
GLsizei width, // 要更新的区域宽度
GLsizei height, // 要更新的区域高度
GLenum format, // 像素数据格式(如 GL_RGBA)
GLenum type, // 像素数据类型(如 GL_UNSIGNED_BYTE)
const void* data // 指向像素数据的指针
);
使用流程:
- 创建初始纹理
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
- 更新部分纹理数据
// 假设更新 128x128 的区域,从 (10, 10) 开始
uint8_t pixelData[128*128*4]; // RGBA格式数据
glBindTexture(GL_TEXTURE_2D, textureID);
glTexSubImage2D(
GL_TEXTURE_2D, 0,
10, 10, // 偏移量 (x, y)
128, 128, // 更新区域尺寸
GL_RGBA, GL_UNSIGNED_BYTE,
pixelData
);
glBindFramebuffer
glBindFramebuffer
用于绑定帧缓冲区对象(FBO)的核心函数,决定了后续所有渲染操作的目标位置(屏幕或离屏缓冲区).
原型:
void glBindFramebuffer(GLenum target, GLuint framebuffer);
- target
GL_FRAMEBUFFER
同时绑定读写GL_READ_FRAMEBUFFER
只读GL_DRAW_FRAMEBUFFER
只写
- framebuffer 帧缓冲区对象ID(由glGenFramebuffers生成),0表示绑定默认窗口缓冲区
核心功能:
1)切换渲染目标
绑定到自定义FBO时,渲染输出到离屏缓冲区(纹理或渲染缓冲区);
绑定到0时,渲染输出到默认帧缓冲区(屏幕).
2)多缓冲区管理
通过不同target分离读写操作,e.g. 用GL_READ_FRAMEBUFFER
读取, GL_DRAW_FRAMEBUFFER
写入
glFramebufferTexture2D
glFramebufferTexture2D
用于将2D纹理附加到帧缓冲区对象(FBO)的关键函数,定义了渲染输出的目标位置(如颜色、深度或模板附件).
void glFramebufferTexture2D(
GLenum target, // 帧缓冲区目标(GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER)
GLenum attachment, // 附件类型(如 GL_COLOR_ATTACHMENT0)
GLenum textarget, // 纹理目标(如 GL_TEXTURE_2D)
GLuint texture, // 纹理对象 ID
GLint level // 纹理的 mipmap 级别(通常为 0)
);
- target
GL_FRAMEBUFFER
读写帧缓冲区GL_READ_FRAMEBUFFER
只读GL_DRAW_FRAMEBUFFER
只写
- attachment
- 颜色附件:
GL_COLOR_ATTACHMENT0
toGL_COLOR_ATTACHMENTi
(i 取决于硬件) - 深度附件:
GL_DEPTH_ATTACHMENT
- 模板附件:
GL_STENCIL_ATTACHMENT
- 深度+模板:
GL_DEPTH_STENCIL_ATTACHMENT
- 颜色附件:
- textarget
GL_TEXTURE_2D
普通2D纹理GL_TEXTURE_CUBE_MAP_POSITIVE_X
立方体贴图面GL_TEXTURE_2D_MULTISAMPLE
MSAA特殊纹理类型,多重采样2D纹理
- texture 纹理对象id,0代表解绑当前附件
- level 纹理的mipmap级别,通常为0,即基础级别
glBlitFramebuffer
glBlitFramebuffer
用于高效复制或缩放帧缓冲区区域的函数, 直接在 GPU上执行像素数据传输(称为 "Blit" 操作),无需 CPU 介入,适合多缓冲区管理和后处理效果.
原型:
void glBlitFramebuffer(
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, // 源区域(左下、右上坐标)
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, // 目标区域(左下、右上坐标)
GLbitfield mask, // 要复制的缓冲区类型
GLenum filter // 缩放过滤器
);