OpenGL超级宝典(第五版)第8章fbo_drawbuffers例子分析

news2024/7/7 16:33:16

目录

1. 概述

2. 疑难点剖析

2.1 SetupRC函数分析

2.2 multibuffer.vs分析

2.3 RenderScene分析

3. 其它


1. 概述

          《OpenGL超级宝典(第五版)》如下:

该书第8版的 fbo_drawbuffers工程展示了如下技术点:

  • 什么是帧缓冲区对象(FBO)。
  • 如何将绘制输出到非默认的帧缓冲区对象上,即如何实现离屏渲染(即不绘制在窗体)。
  • 如何使用FBO,包括FBO的创建、销毁、映射、完整性检查、在帧缓冲区中复制数据。
  • 什么是渲染缓冲区对象(RBO),包括RBO的创建、设置RBO大小、销毁。
  • 如何将FBO、RBO、纹理缓冲区(TBO)结合在一起。
  • glDrawBuffers、glReadBuffers、glBlitFramebuffer、glBindFragDataLocation函数的作用。

说明:请先把OpenGL超级宝典(第五版)第8章中有关上述的知识点,自己研究一遍,扫清基本的概念后再读本博文。

2. 疑难点剖析

2.1 SetupRC函数分析

在该函数中有如下代码:

gltMakeTorus(torusBatch, 0.4f, 0.15f, 35, 35);
gltMakeSphere(sphereBatch, 0.1f, 26, 13);

这两句代码,没啥用,可以注释掉,估计编者将前几章例子中的代码直接复制过来改成本例子时,忘记删除这两句了。

该函数通过如下代码加载忍者(受过日本传统打斗和轻功训练的人):

 ninja.LoadFromSBM("../../../Src/Models/Ninja/ninja.sbm",
        GLT_ATTRIBUTE_VERTEX,
        GLT_ATTRIBUTE_NORMAL,
        GLT_ATTRIBUTE_TEXTURE0);

转到LoadFromSBM函数:

bool SBObject::LoadFromSBM(const char * filename, int vertexIndex, int normalIndex, int texCoord0Index)
{
    FILE * f = NULL;

    f = fopen(filename, "rb");

    fseek(f, 0, SEEK_END);
    size_t filesize = ftell(f);
    fseek(f, 0, SEEK_SET);

    unsigned char * data = new unsigned char [filesize];
    unsigned char * raw_data;
    fread(data, filesize, 1, f);
    fclose(f);

    SBM_HEADER * header = (SBM_HEADER *)data;
    raw_data = data + sizeof(SBM_HEADER) + header->num_attribs * sizeof(SBM_ATTRIB_HEADER) + header->num_frames * sizeof(SBM_FRAME_HEADER);
    SBM_ATTRIB_HEADER * attrib_header = (SBM_ATTRIB_HEADER *)(data + sizeof(SBM_HEADER));
    SBM_FRAME_HEADER * frame_header = (SBM_FRAME_HEADER *)(data + sizeof(SBM_HEADER) + header->num_attribs * sizeof(SBM_ATTRIB_HEADER));
    unsigned int total_data_size = 0;

    memcpy(&m_header, header, sizeof(SBM_HEADER));
    m_attrib = new SBM_ATTRIB_HEADER[header->num_attribs];
    memcpy(m_attrib, attrib_header, header->num_attribs * sizeof(SBM_ATTRIB_HEADER));
    m_frame = new SBM_FRAME_HEADER[header->num_frames];
    memcpy(m_frame, frame_header, header->num_frames * sizeof(SBM_FRAME_HEADER));

    glGenVertexArrays(1, &m_vao);
    glBindVertexArray(m_vao);
    glGenBuffers(1, &m_attribute_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_attribute_buffer);

    unsigned int i;

    for (i = 0; i < header->num_attribs; i++) {
        int attribIndex = i;

        if(attribIndex == 0)
            attribIndex = vertexIndex;
        else if(attribIndex == 1)
            attribIndex = normalIndex;
         else if(attribIndex == 2)
            attribIndex = texCoord0Index;

        glVertexAttribPointer(attribIndex, m_attrib[i].components, m_attrib[i].type, GL_FALSE, 0, (GLvoid *)total_data_size);
        glEnableVertexAttribArray(attribIndex);
        total_data_size += m_attrib[i].components * sizeof(GLfloat) * header->num_vertices;
    }

    glBufferData(GL_ARRAY_BUFFER, total_data_size, raw_data, GL_STATIC_DRAW);

    if (header->num_indices) {
        glGenBuffers(1, &m_index_buffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
        unsigned int element_size;
        switch (header->index_type) {
            case GL_UNSIGNED_SHORT:
                element_size = sizeof(GLushort);
                break;
            default:
                element_size = sizeof(GLuint);
                break;
        }
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, header->num_indices * element_size, data + total_data_size, GL_STATIC_DRAW);
    }

    glBindVertexArray(0);

    delete [] data;

    return true;
}

该函数前面部分根据ninja.sbm文件的内定格式,解析出相应格式的数据并存放到对应的结构体中用作后续绘图。该函数的后半部是关注的重点,其流程为:

  1. 创建一个VAO(顶点数组对象)并绑定这个VAO。
  2. 创建一个VBO(顶点缓冲区对象)并绑定到GL_ARRAY_BUFFER。
  3. 通过glVertexAttribPointer函数定义ninja.sbm文件中包含的通用顶点的数据在VBO中的排列格式,以便绘制时,GPU能识别并解析该数据。调用glEnableVertexAttribArray使相应的顶点数组变为有效状态,以便GPU能读取数据。
  4. 通过glBufferData函数将ninja.sbm文件中包含的通用顶点数据填充到步骤2中创建的VBO对象。

glVertexAttribPointer、glEnableVertexAttribArray、VAO、VBO之间的关系,请参见理解glVertexAttribPointer、glEnableVertexAttribArray、VAO、VBO的关系   博文。

接下来是如下代码:

.........................   // 其它代码略

    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBufferNames[0]);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, renderBufferNames[1]);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, renderBufferNames[2]);

	// See bind frag location in Chapter 9
    processProg =  gltLoadShaderPairWithAttributes("multibuffer.vs", "multibuffer_frag_location.fs", 3,
								GLT_ATTRIBUTE_VERTEX, "vVertex", 
								GLT_ATTRIBUTE_NORMAL, "vNormal", 
								GLT_ATTRIBUTE_TEXTURE0, "texCoord0");
	glBindFragDataLocation(processProg, 0, "oStraightColor");
	glBindFragDataLocation(processProg, 1, "oGreyscale");
	glBindFragDataLocation(processProg, 2, "oLumAdjColor"); 

.........................   // 其它代码略

其中glBindFragDataLocation函数的具体用法参见:glBindFragDataLocation函数说明

该函数定义如下:

void glBindFragDataLocation(	GLuint program,
                                GLuint colorNumber,
                                const char * name);

        该函数功能为:绑定一个用户自定义的varying类型的输出变量到片段着色器的颜色号上。参数 program表示着色器程序的句柄;colorNumber表示绑定到用户自定义的varying类型的输出变量上的颜色号,对于本例来说就是GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1、GL_COLOR_ATTACHMENT2这三个枚举值后面的数字0、1、2;name表示片段着色器中用户自定义的varying类型的输出变量的名称,该变量会绑定到GL_COLOR_ATTACHMENTN(N为0到15)。

         说白了,上面三句glBindFragDataLocation代码功能就是把片段着色器multibuffer_frag_location.fs中的如下三个输出变量表示的颜色值:

..................  // 其它代码略,具体参见multibuffer_frag_location.fs


out vec4 oStraightColor;
out vec4 oGreyscale;
out vec4 oLumAdjColor;


..................  // 其它代码略,具体参见multibuffer_frag_location.fs

传递给GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1、GL_COLOR_ATTACHMENT2连接的渲染缓冲区,而渲染缓冲区是在帧缓冲区中的,帧缓冲区只是一个容器,经过这三句代码后,GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1、GL_COLOR_ATTACHMENT2表示的渲染缓冲区也即帧缓冲区的颜色就分别由片段着色器oStraightColor、oGreyscale、oLumAdjColor的值来决定,于是通过改变片段着色器这三个颜色值,就可以相应地改变GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1、GL_COLOR_ATTACHMENT2表示的渲染缓冲区也即帧缓冲区颜色。

       如下代码:

    ......................// 其它代码略

    // Load the Tan ramp first
	glBindBuffer(GL_TEXTURE_BUFFER_ARB, 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_BUFFER_ARB, texBOTexture);
	glTexBufferARB(GL_TEXTURE_BUFFER_ARB, GL_R32F, texBO[0]); 
	glActiveTexture(GL_TEXTURE0);
 
    ......................// 其它代码略

这段代码需要注意的是:调用glActiveTexture(GL_TEXTURE1);将纹理单元1 设置为当前活动纹理,接下来设置纹理单元1的纹理数据为texBO[0]缓冲区存放的数据,即LumTan.data文件中的表示的纹理数据。注意,这里用到了多重纹理,GL_TEXTURE0为大理石表示的纹理,而GL_TEXTURE1表示LumTan.data文件中的表示的纹理数据,这样就在大理石纹理上加上了一层类似光强度的纹理,使大理石纹理和光强度纹理糅合在一起,形成视觉上的效果。

2.2 multibuffer.vs分析

multibuffer.vs可以参考本书的第6章的6.14、6.15例子。

2.3 RenderScene分析

该函数有如下代码:

        ......................... // 其它代码略
	    if(bUseFBO)
		{
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboName);
			glDrawBuffers(3, fboBuffs);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			// Need light position relative to the Camera
			M3DVector4f vLightTransformed;
			m3dTransformVector4(vLightTransformed, vLightPos, mCamera);
			UseProcessProgram(vLightTransformed, vFloorColor, 0);
		}
		else
		{
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
			glDrawBuffers(1, windowBuff);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE, 
            transformPipeline.GetModelViewProjectionMatrix(), vFloorColor, 0);
		}

     ......................... // 其它代码略

    if(bUseFBO)
	{
		// Direct drawing to the window
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
		glDrawBuffers(1, windowBuff);
		glViewport(0, 0, screenWidth, screenHeight);

		// Source buffer reads from the framebuffer object
		glBindFramebuffer(GL_READ_FRAMEBUFFER, fboName);

		// Copy greyscale output to the left half of the screen
		glReadBuffer(GL_COLOR_ATTACHMENT1);
		glBlitFramebuffer(0, 0, screenWidth/2, screenHeight,
						  0, 0, screenWidth/2, screenHeight,
						  GL_COLOR_BUFFER_BIT, GL_NEAREST );
	
		// Copy the luminance adjusted color to the right half of the screen
		glReadBuffer(GL_COLOR_ATTACHMENT2);	
		glBlitFramebuffer(screenWidth/2, 0, screenWidth, screenHeight,
						  screenWidth/2, 0, screenWidth, screenHeight,
						  GL_COLOR_BUFFER_BIT, GL_NEAREST );

		// Scale the unaltered image to the upper right of the screen
		glReadBuffer(GL_COLOR_ATTACHMENT0);
		glBlitFramebuffer(0, 0, screenWidth, screenHeight,
						  (int)(screenWidth *(0.8)), (int)(screenHeight*(0.8)), 
						  screenWidth, screenHeight,
						  GL_COLOR_BUFFER_BIT, GL_LINEAR );

		glBindTexture(GL_TEXTURE_2D, 0);
	} 


......................... // 其它代码略

本段代码块说明了如果采用和不采用FBO时,如何绘制,其基本逻辑是:

  1. 在第4行调用glBindFramebuffer函数将绘制的目标设置为帧缓冲区。 即当前绘制的所有对象都绘制到帧缓冲区上,而不是绘制到窗体,即离屏渲染。根据SetupRC函数有关帧缓冲区和渲染缓冲区的挂接关系,可以知道,离屏渲染对象为:GL_DEPTH_ATTACHMENT、GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1、GL_COLOR_ATTACHMENT2
  2. 通过glDrawBuffers函数进一步限定步骤1中要绘制到哪个颜色缓冲区。关于该函数的具体用法,参见:glDrawBuffers用法。
  3. 第13行到20行表示如果不启用帧缓冲区绘制,则直接绘制到窗体上。第15行调用glBindFramebuffer函数将绘制的目标设置为窗体。注意:帧缓冲区默认绘制目标是窗体,当将glBindFramebuffer函数的第2个参数设置为0时,表示解除以前绑定的绘制目标且恢复到默认目标上,0就是表示默认目标即窗体。程序在创建窗体时,OPenGL会自动将默认帧缓冲区挂接到该窗体上。类似地,通过glDrawBuffers函数指出绘制到窗体的哪个缓冲区。
  4. 步骤1到步骤3说明了要绘制到哪里,第22行省略的代码则是具体绘制代码。
  5. 第27到29行代码则是将绘制目标切换到窗体,并设置窗体视口范围。
  6. 第32行,通过设置glBindFramebuffer函数的第1个参数为GL_READ_FRAMEBUFFER,从而设置从帧缓冲区读取像素,注意:glBindFramebuffer函数的第1个参数为GL_READ_FRAMEBUFFER对glBindFramebuffer函数的第1个参数为GL_DRAW_FRAMEBUFFER的功能不影响,即绘制写入目标依然是窗体。第35行设置从帧缓冲区的GL_COLOR_ATTACHMENT1表示的颜色缓冲区读像素,接下来调用glBlitFramebuffer函数将从GL_COLOR_ATTACHMENT1表示的颜色缓冲区中的一块矩形范围中的像素读取到窗体指定的矩形范围中。注意,该函数是在GPU中各个不同缓冲区传输数据,不经过CPU,故效率很高。关于glBlitFramebuffer函数用法,参见:glBlitFramebuffer函数用法。这样就把GL_COLOR_ATTACHMENT1表示的颜色缓冲区中的数据绘制到窗体上了,根据前面对SetupRC函数的讲解,GL_COLOR_ATTACHMENT1是和片段着色器中的oGreyscale输出颜色对应的,即oGreyscale表示的数据就绘制到了窗体左半窗体上了。GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT2逻辑相同,不再分析。

MoveCamera()句代码没啥用,删除掉,否则链接时会报找不到该函数。估计编者将前几章例子中的代码直接复制过来改成本例子时,忘记删除这两句了。

3. 其它

    其它代码很简单,请参见书本中对该例子的分析,不再赘述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1040245.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程间通信方式②——文件共享映射(附图解与代码实现)

我们来简单了解下文件共享映射的定义&#xff1a;通过映射文件&#xff0c;使用映射机制&#xff0c;实现资源共享&#xff0c;完成进程通信 具体是如何实现的呢&#xff1f;跟随着这篇博客&#xff0c;我们来看一看 进程通过文件共享映射实现通信的具体步骤 由某一进程创建映…

【湖科大教书匠】计算机网络随堂笔记第2章(计算机网络物理层)

目录 2.1、物理层的基本概念 2.2、物理层下面的传输媒体 导引型传输媒体 同轴电缆 双绞线 光纤 电力线 非导引型传输媒体 无线电波 微波 红外线 可见光 2.3、传输方式 串行传输和并行传输 同步传输和异步传输 单向通信&#xff08;单工&#xff09;、双向交替通信&#xf…

【LeetCode-简单题】110. 平衡二叉树

文章目录 题目方法一&#xff1a;后序递归 题目 方法一&#xff1a;后序递归 递归遍历的同时判断是否是平衡二叉树&#xff0c;如果不是&#xff0c;就置为-1&#xff0c;如果是 就正常做递归求最大深度 参考图解网址 判断平衡二叉树 class Solution {public boolean isBalanc…

誉天在线项目-放大招-Vue3集成RichText富文本客户端组件QuillEditor

背景 开发中我们需要填写图文内容&#xff0c;就是含有图片和文字&#xff0c;html标准组件中是没有的。都是第三方来实现&#xff0c;就需要我们去集成。 有早期的fckEditor、ckEditor等&#xff0c;新的我们使用了vue框架&#xff0c;市场又推出了quillEditor。下面我们就在…

【【萌新的SOC学习之绪论】】

萌新的SOC学习之绪论 Vitis 统一软件平台的前身为 Xilinx SDK&#xff0c;从 Vivado 2019.2 版本开始&#xff0c;Xilinx SDK 开发环境已统一整合 到全功能一体化的 Vitis 中。Vitis 开发平台除了启动方式、软件界面、使用方法与 SDK 开发平台略有区别&#xff0c; 其他操作几…

使用acme.sh申请免费ssl证书(Cloudflare方式API自动验证增加DNS Record到期证书到期自动重新申请)

下载acme.sh curl https://get.acme.sh | sh -s emailmyexample.comcd ~/.acme.sh/获取Cloudflare密钥 Preferences | Cloudflare 登录选择账户详情选择API Token选择创建令牌选择区域DNS模板&#xff0c;并设置编辑写入权限生成并复制令牌备用回到首页概览界面下部获取账号…

【RabbitMQ实战】3分钟在Linux上安装RabbitMQ

本节采用docker安装RabbitMQ。采用的是bitnami的镜像。Bitnami是一个提供各种流行应用的Docker镜像和软件包的公司。采用docker的方式3分钟就可以把我们想安装的程序运行起来&#xff0c;不得不说真的很方便啊&#xff0c;好了&#xff0c;开搞。使用前提&#xff1a;Linux虚拟…

3D设计软件Rhinoceros 6 mac 犀牛6中文版功能特征

Rhinoceros Mac中文版是一款3D设计软件“犀牛”&#xff0c;在众多三维建模软件中&#xff0c;Rhinoceros mac因为其体积小、功能强大、对硬件要求低而广受欢迎&#xff0c;对于专业的3D设计人员来说它是一款非常不错的3D建模软件&#xff0c;Rhinoceros Mac中文版能轻易整合3D…

苹果手机短信删除了怎么恢复?3种有效方法介绍

手机短信是一种即时通信方式&#xff0c;人们可以使用短信来达到快速传递信息的目的。在没有网络或者网络不稳定的时候&#xff0c;短信仍然可以做到发送和接收&#xff0c;这弥补了其他网络通信软件的缺点。 所以说&#xff0c;手机短信仍然是我们生活中不可缺少的一部分。当…

【rtp】mid 扩展: RtpMid 字符串扩展的解析和写入

mid 是uint8_t 类型? 扩展填写的是字符串,读取字符串后atoi 转 uint8_t : webrtc 看起来是个字符串:写入 扩展的值是改变了: 这里是更新扩展的长度: 新的大小小于原来的,没有缩减内存,而是对于多余的置位0了:if (len < current_len) {memset(

基于SSM的高校图书馆个性化服务的设计与实现(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的高校图书馆个性化服务的设计与实现&#xff08;有报告&#xff09;。Javaee项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过S…

如何在低代码平台中应用可视化编程

可视化编程&#xff0c;亦即可视化程序设计&#xff1a;以“所见即所得”的编程思想为原则&#xff0c;力图实现编程工作的可视化&#xff0c;即随时可以看到结果&#xff0c;程序与结果的调整同步。可视化编程的理念来源于可视化技术&#xff0c;它指的是一种把计算机程序中的…

深度学习在视频直播美颜sdk中的应用

视频直播美颜SDK是一类用于实时视频美颜处理的工具包&#xff0c;它们利用深度学习算法来提高视频直播中的主播和观众的外观吸引力。本文将深入探讨深度学习在视频直播美颜sdk中的应用&#xff0c;以及这些应用对直播行业的重要性。 一、人脸检测与关键点定位 通过卷积神经网…

Centos7安装go解释器

Centos7安装go解释器 下载解压go压缩包编辑go变量结果验证 下载解压go压缩包 # 下载 wget -c https://go.dev/dl/go1.20.2.linux-amd64.tar.gz# 解压到指定目录 tar xvf go1.20.2.linux-amd64.tar.gz -C /usr/local/编辑go变量 /etc/profile.d/go.sh # 指定go执行程序位置 e…

多线程进阶:常见的锁策略、CAS

之前我们所了解的属于多线程的初阶内容。今天开始&#xff0c;我们进入多线程进阶的学习。 锁的策略 乐观锁 悲观锁 这不是两把具体的锁&#xff0c;应该叫做“两类锁” 乐观锁&#xff1a;预测锁竞争不是很激烈&#xff08;这里做的工作可能就会少一些&#xff09; 悲观锁…

【操作系统笔记六】内存分配

内存对齐 问题&#xff1a;为什么需要内存对齐呢&#xff1f; 主要原因是为了兼容&#xff0c;为了让程序可以运行在不同的处理器中&#xff0c;有很多处理器在访问内存的时候&#xff0c;只能从特定的内存地址读取数据。换个说法就是处理器每次只能从内存取出特定个数字节的数…

WKB近似

WKB方法用于研究一种特定类型的微分方程的全局性质 很有用这种特定的微分方程形如&#xff1a; 经过一些不是特别复杂的推导&#xff0c;我们可以得到他的WKB近似解。 该近似解的选择取决于函数和参数的性质同时&#xff0c;我们默认函数的定义域为当恒大于零,时&#xff1a; 当…

淘宝/天猫获得淘宝商品详情(关键词搜索,店铺所有商品)API接口返回值说明

淘宝API接口&#xff0c;简单而言&#xff0c;就是一套工具&#xff0c;可以帮助你与淘宝平台的数据与功能进行智能对接。它能够让你的店铺信息、商品信息、用户数据等信息实现高效流通&#xff0c;帮助你更好地理解客户需求 我们深知数据安全的重要性&#xff0c;因此&#x…

【MySQL】MySQL中的复制技术是什么?它有哪些组成部分?

什么是复制&#xff08;Replication&#xff09;MySQL复制架构感谢 &#x1f496; 什么是复制&#xff08;Replication&#xff09; 复制技术是MySQL高级特性的基础。它可以将数据从一个 MySQL 实例复制到另一个实例&#xff0c;从而实现数据的同步和备份。 MySQL复制架构 以…

Ansys Zemax | 如何设计光谱仪——理论依据

光谱学是一种无创性技术&#xff0c;是研究组织、等离子体和材料的最强大工具之一。本文介绍了如何利用近轴元件建立透镜—光栅—透镜(LGL)光谱仪模型&#xff0c;使用OpticStudio的多重结构( Multiple Configurations )、评价函数 ( Merit Functions )和ZPL宏等先进功能完成了…