Qt5+OpenGL学习笔记(新手向):一(2)、OpenGL中Buffer的初步认识



  • 前面的话。。。
    这次简单介绍了下我学习时觉得比较关键的基本知识,是基于OpenGL原生函数的讲解。Qt有一套对OpenGL的封装,就是QOpenGL系列。我打算在写完shader的笔记之后将原生写法和使用Qt所封装的类的写法都介绍下。还有就是红宝书是上来就开始讲shader的概念的,但是我觉得这样不太好理解(是不是我太笨了TvT)。。。

    Buffer Object(缓存对象)
    OpenGL几乎所有的事情都用到了缓存中的数据。
    在上一个笔记中,我渲染了一个简单的三角形。基本步骤
    先给出了顶点数据:
    GLfloat verts[]={
    +0.0f, +1.0f,
    +1.0f, -1.0f,
    -1.0f, -1.0f,
    };
    每两个浮点数一组表示一个顶点的X,Y坐标,这里没有给出Z的坐标(应该是默认为0.0了)。关于Z的坐标在写Shader(着色器)的笔记的时候会详细解释。
    有了顶点数据之后,要将顶点数据放入OpenGL的缓存当中。
    首先,我们要创建一块OpenGL中的缓存。
    void glGenBuffers(GLsizei n,GLuint* buffers);
    返回n个当前未使用的缓存对象名称,并保存到buffers数组中。
    对应的代码:生成了一个缓存对象(BufferObject)的名称,给它赋值给myBufferID。
    GLuint myBufferID;
    glGenBuffers(1,&myBufferID);
    之后,要把缓存对象绑定到指定的的缓存结合点。
    void glBindBuffer(GLenum target,GLuint buffer);
    将名为buffer的缓存对象绑定到target所指定的缓存结合点。
    对应的代码:(将myBufferID绑定到GL_ARRAY_BUFFER上)
    glBindBuffer(GL_ARRAY_BUFFER,myBufferID);
    我们已经生成了一个缓存对象并绑定到了指定的缓存结合点。之后我们要向缓存中输入数据。
    glBufferData(GLenum target,GLsizeiptr size,const GLvoid* data,GLenum usage);
    为绑定到target的缓存对象分配size大小(单位为字节)的存储空间。如果参数data不是NULL,那么将使用data所在的内存区域的内容来初始化整个空间。Usage允许应用程序向OpenGL端发出一个提示,指示缓存中的数据可能具备一定的特殊用途。
    对应的代码:分配相应的缓存空间
    glBufferData(GL_ARRAY_BUFFER,sizeof(verts),verts,GL_STATIC_DRAW);
    至此呢,OpenGL已经在缓存中存储了我们所需要的数据。但是OpenGL不知道这些数据的含义,需要我们为它指明。
    void glEnableVertexAttribArray(GLuint index);
    void glDisableVertexAttribArray(GLuint index);
    设置是否启用与index索引相关联的顶点数组。Index必须是一个介于0到GL_MAX_VERTEX_ATTRIBS-1之间的值。

    void glVertexAttribPointer(GLuint index,GLuint size,GLenum type,GLboolean normalized,GLsizei stride,const GLvoid *pointer);
    设置index(着色器中的属性位置)位置对应的数据值。pointer表示缓存对象中,从开始位置开始计算的数组数据的偏移值(假设起始地址为0),使用基本的系统单位(byte)。size表示每个顶点需要更新的分量数目。type指定了数组中每个元素的数据类型。Normalized设置顶点数据在存储前是否需要归一化。stride是数组中每两个元素的大小偏移值(byte)。如果stride为0,那么数据应该紧密的封装在一起。
    对应的代码:启用索引为0的顶点数组。从头开始读取,每个元素含两个浮点型数据,不采取归一化,每个数据紧密相连。
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,0);
    最后在paintGL()中使用绘制指令
    void glDrawArrays(GLenum mode,GLint first,GLsizei count);
    使用数组元素建立连续的几何图元序列,每个启用的数组中的起始位置为first,结束位置为first+count-1。mode为构建图元的类型。
    对应的代码:绘制三角形,其实位置为0,结束位置为2。
    glDrawArrays(GL_TRIANGLES,0,3);

    后面的话:感觉这次写的乱了一点。主要是初学自己也没理解透,可能有表述不清之处Orz。
    附带两个将这方面的传送门:
    讲OpenGL中Buffer的:
    http://www.bubuko.com/infodetail-700038.html
    讲VAO,VBO的:
    http://blog.csdn.net/zhuyingqingfen/article/details/19238651

    Ps:初学时的坐标系简介
    在学习视口变换等内容之前,我们渲染的图形对应的坐标系如下。以绘图窗口的中心为原点,向左为X轴正向,最大值为+1.0。向右为X轴负向,最小值为-1.0。向上为Y轴正向,最大值为+1.0。向下为Y轴负向,最小值为-1.0。垂直XOY平面指向屏幕外为Z轴正向,最大值为+1.0。垂直XOY平面指向屏幕内为Z轴负向,最小值为-1.0。(画了个示意图如下,Z轴没有画出)
    0_1454503571179_upload-3e6509ec-ec5f-44ba-9810-2e02881753df



  • 弱弱问一句,博客发了之后不能修改么。。。



  • @MrXiaoXiao 可以进行修改。只要左边有齿轮的按钮,可以设置。



  • 十分期待后续啊~不能太监啊~



  • 关注一波,opengl大法好



  • @MrXiaoXiao 下文(面)没有了?



  • @qyvlik 我也在期待他的下文,不过这个ID好像好久没来了~


  • 网站研运

    这个博客讲的是OpenGL的概念。
    Qt对此进行了封装。
    比如说顶点以及索引缓存对象为:QOpenGLBuffer,纹理对象为:QOpenGLTexture,FBO对象为QOpenGLFrameBufferObject
    最后推荐看看我的github。里面有一些简单的OpenGL和Qt相关的小例子,大家可以参考一下。

    请点击我



  • 安利一下这个:http://learnopengl.com/, 中文版 https://learnopengl-cn.readthedocs.io

    最近在看这个和彩阳的博客学习Qt3D的框架,捣鼓了一个大炮打蚊子的东西 https://github.com/MidoriYakumo/learnopengl-qt3d


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群