SoftGLRender源码:视图管理器ViewManager

特性

文件:ViewManager.h

ViewManager类管理着各种与显示有关的重要内容,它本身不产生任何显示资源,而是控制着这些资源. 主要包括:

  • 配置数据(config_),交互的配置面板(configPanel_)
  • 用于人机交互的轨道控制器(orbitController_)
  • 3D模型加载器,场景资源(modelLoader_)
  • 用于显示3D内容的视图(viewers_)
  • 用于观察3D内容相机(camera_)

类图关系

img

  • 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激活纹理对象. 成功后,所有后续纹理操作(如glTexImage2DglTexParameteri)将作用于该纹理.

原型:

void glBindTexture(GLenum target, GLuint texture);
  • target 纹理类型,常用值:
    • GL_TEXTURE_2D(2D纹理)
    • GL_TEXTURE_CUBE_MAP(立方体贴图)
    • GL_TEXTURE_3D(3D纹理)
  • texture 纹理对象的ID(glGenTextures生成),0表示解绑当前纹理.

使用流程:

  1. 创建并绑定纹理
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);
  1. 渲染时绑定纹理
glActiveTexture(GL_TEXTURE0);          // 选择纹理单元0
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理
  1. 解绑纹理
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        // 指向像素数据的指针
);

使用流程:

  1. 创建初始纹理
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);
  1. 更新部分纹理数据
// 假设更新 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 to GL_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                                       // 缩放过滤器
);

参考

Z-Fighting问题解决(二) - Reverse-z

RenderDoc 使用介绍

移动端高性能图形开发 - 详解MSAA

延迟渲染与MSAA的那些事

posted @ 2025-06-15 23:16  明明1109  阅读(44)  评论(0)    收藏  举报