《OpenGL ES 3.x游戏开发(下卷)》一1.4 映射缓冲区对象

1.4 映射缓冲区对象

前面几节已经介绍了顶点缓冲区对象、顶点数组对象以及一致缓冲区对象,通过使用这些技术可以在很大程度上提高绘制效率。本节将介绍在某些情况下可以进一步提高效率的映射缓冲区对象(Mapping Buffer Objects)。

1.4.1 基本知识与案例效果

本章前面的几个案例中,都是通过调用glBufferData方法或者glBufferSubData方法向缓冲区中送入数据或者更新数据的。采用这种策略时需要首先将数据在内存中准备好,然后再通过glBufferData方法或glBufferSubData方法将数据从内存复制到显存中。

这对于缓冲中数据不变或变化率很低的情况基本够用了,但是对于绘制过程中频繁变化的数据就显得效率不够高。本小节将介绍一种针对此问题的解决方案——映射缓冲区对象。通过使用映射缓冲区对象,可以在绘制过程中数据频繁变化的情况下进一步减少内存消耗并提高渲染效率。

提示

所谓映射缓冲区对象就是将显存中的存储映射到虚拟的内存地址上,使得开发人员可以使用如同访问内存一样的API访问显存以提高效率。

使用映射缓冲区对象主要涉及的方法有3个,具体内容如下。

  • glMapBufferRange方法

glMapBufferRange方法用于将指定缓冲对应的显存映射到虚拟的内存地址上,并返回映射的结果,以便开发人员使用它来更新显存中的数据。如果出现错误或者发出无效请求,该方法将返回空,其具体方法签名如下。

1    public static Buffer glMapBufferRange (int target, int offset, int length, int access)

说明

参数target用于描述需映射的缓冲区类型,可以设置的值如本章前面表1-1所列;参数offset为被映射的缓冲区数据存储中的偏移量;参数length为需要映射的缓冲区数据字节数;参数access为访问标志,可选的访问标志如表1-6所列。

提示

表1-6中的访问标志在不冲突的情况下可以同时使用多个标志,使用多个标志时用“|”隔开。另外,实际开发中一般至少选用GL_MAP_READ_BIT与GL_MAP_WRITE_BIT中的一个,而其他选项则进一步根据需要选择即可。

  • glUnmapBuffer方法

glUnmapBuffer方法用于解除缓冲区映射,其具体方法签名如下。

1 public static boolean glUnmapBuffer (int target)

说明

参数target用于描述需解除映射的缓冲区类型,可以设置的值如本章前面表1-1所列。如果解除映射操作成功,则返回true,并且前面glMapBufferRange方法返回的映射范围在取消映射操作成功之后不再可用。如果顶点缓冲区对象数据存储中的数据在缓冲区映射之后已经破坏,则glUnmapBuffer方法返回false。

  • glFlushMappedBufferRange方法

glFlushMappedBufferRange方法用于通知渲染管线被映射缓冲区中的数据已经被修改,类似于I/O操作时用于刷新数据的flush方法,其具体方法签名如下。

1    public static void glFlushMappedBufferRange (int target, int offset, int length)

说明

参数target用于描述需刷新数据所属的被映射缓冲区类型;参数offset为被映射的缓冲区数据存储中的偏移量;参数length为需要刷新的被映射缓冲区数据字节数。需要注意的是,glFlushMappedBufferRange方法所操作的缓冲必须用glMapBufferRange方法在映射时选用了GL_MAP_FLUSH_EXPLICIT_BIT选项。

了解了映射缓冲区对象的基本知识以后,就可以进行案例的开发了。在开发案例之前,首先应该了解本节案例Sample1_4的运行效果,具体情况如图1-4所示。


说明

从图1-4中可以看出,运行过程中球体的上半部分在球体与立方体之间连续变换着。这是由于运行过程中程序不断进行缓冲区映射,并连续更新球体上半部分的顶点数据。

1.4.2 案例开发步骤

了解了映射缓冲区对象的基本知识与案例效果后,就可以进行代码的开发了。由于本案例中的很多类与前面案例中的很相似,因此这里仅给出本案例中具有特殊性及代表性的代码,具体内容如下。

(1)首先介绍的是BallAndCube类中用于初始化顶点数据的initVertexData方法,在该方法中向顶点坐标数据缓冲中送入数据时就采用了映射缓冲区,具体内容如下。

1    public void initVertexData() {                    //初始化顶点数据的方法
2         int[] buffIds=new int[3];                    //用于存放缓冲id的数组
3         GLES30.glGenBuffers(3, buffIds, 0);          //生成3个缓冲id
4         mVertexBufferId=buffIds[0];                  //顶点坐标数据缓冲 id
5         mTexCoorBufferId=buffIds[1];                 //顶点纹理坐标数据缓冲 id
6         mIndicesBufferId=buffIds[2];                 //顶点索引数据缓冲id
7         ……//此处省略了用于初始化纹理坐标缓冲区和索引缓冲区的代码,需要的读者请参考随书
8         GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mVertexBufferId);//绑定顶点坐标数据缓冲
9         GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertices.length*4,
10                 null, GLES30.GL_STATIC_DRAW);       //开辟缓冲存储
11        vbb1=(ByteBuffer)GLES30.glMapBufferRange(    //映射顶点坐标数据缓冲
12         GLES30.GL_ARRAY_BUFFER,                     //缓冲类型
13         0,                                          //偏移量
14         vertices.length*4,                          //长度(以字节计)
15         GLES30.GL_MAP_WRITE_BIT|GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);//访问标志
16        if(vbb1==null){return;}                      //若映射失败则返回
17        vbb1.order(ByteOrder.nativeOrder());         //设置字节顺序
18        mVertexMappedBuffer=vbb1.asFloatBuffer();    //转换为Float型缓冲
19        mVertexMappedBuffer.put(vertices);           //向映射缓冲区中放入顶点坐标数据
20        mVertexMappedBuffer.position(0);             //设置缓冲区起始位置
21        if(GLES30.glUnmapBuffer(GLES30.GL_ARRAY_BUFFER)==false){return;}//解除缓冲映射
22        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0); //绑定到系统默认缓冲
23    }

说明

上述代码中最有代表性的就是第8~第21行,其中没有使用传统的glBufferData方法向顶点坐标数据缓冲中送入数据,而是使用glMapBufferRange方法先进行缓冲映射,然后再直接将数据送入映射后的缓冲中。需要注意的是,每次映射并更新数据完毕后,都需要使用glUnmapBuffer方法解除映射,否则渲染管线在绘制时,无法正常使用被映射缓冲区中的数据。

(2)了解了用于初始化顶点数据的initVertexData方法后,接下来要介绍的是用于在运行过程中连续计算顶点坐标的方法calVertices和插值方法insertValue,具体代码如下。

1    public void calVertices(int count,boolean flag){      //计算顶点坐标数据的方法
2         for(int i=0;i<vertices.length/2;i++){            //遍历顶点
3              curBallForCal[i]=insertValue(vertices[i],verticesCube[i],span,count,
               flag);    //调用插值方法
4         }
5         synchronized(lock){            //加锁同步,避免多线程并发操作可能带来的问题
6             curBallForDraw=Arrays.copyOf(curBallForCal, curBallForCal.length);
              //复制数据
7    }}
8    public float insertValue(float start,float end,float span,int count,boolean isB
     allToCubeY){ //插值方法
9         float result=0;
10        if(isBallToCubeY){                            //如果是球到立方体的变化
11              result=start+count*(end-start)/span;    //进行顶点坐标插值计算
12        }else{                                        //如果是立方体到球的变化
13              result=end-count*(end-start)/span;      //进行顶点坐标插值计算
14        }
15        return result;                                //返回插值结果
16    }
  • 第1~第7行为在运行过程中不断被调用以计算当前顶点坐标位置为从球到立方体或从立方体到球变化服务的calVertices方法。此方法每次被调用时遍历每个顶点坐标,根据count参数以及flag参数值调用insertValue方法完成插值计算。需要特别注意的是第5~第7行,每次计算完成后,将数据复制进绘制用数组时都需要加锁,以避免复制的同时进行绘制造成画面撕裂的问题。
  • 第8~第15行为在球与立方体之间进行顶点坐标插值计算的insertValue方法,从代码中可以看出此方法采用的是线性插值。
    (3)接下来详细介绍用于更新顶点数据到映射缓冲区的方法updateMapping,其功能为根据接收到的顶点坐标数据更新顶点缓冲区对象中的数据,具体代码如下。
1    public void updateMapping(float[] currVertex){
2         GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,
3                            mVertexBufferId);      //绑定到顶点坐标数据缓冲
4         vbb1=(ByteBuffer)GLES30.glMapBufferRange(GLES30.GL_ARRAY_BUFFER, 0,
5              currVertex.length*4,GLES30.GL_MAP_WRITE_BIT|
6              GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);//进行缓冲区映射
7         if(vbb1==null){return;}                   //若映射失败则返回
8         vbb1.order(ByteOrder.nativeOrder());      //设置字节顺序
9         mVertexMappedBuffer=vbb1.asFloatBuffer(); //转换为Float型缓冲
10        mVertexMappedBuffer.put(currVertex);      //向映射的缓冲区中放入顶点坐标数据
11        mVertexMappedBuffer.position(0);          //设置缓冲区起始位置
12        if(GLES30.glUnmapBuffer(GLES30.GL_ARRAY_BUFFER)==false){return;}//解除映射
13    }

说明

上述方法在运行中定时被调用,用于将计算出来的新的顶点坐标数据更新到对应的缓冲中,供渲染管线在绘制时使用,主要套路与前面initVertexData方法中的对应部分相同。

(4)了解了在绘制过程中不断被调用以便计算与更新顶点坐标的相关方法后,下面来了解一下定时执行BallAndCube类中calVertices方法的线程类——UpdateThread,其具体代码如下。

1    package com.bn.Sample1_4;                                    //声明包名
2    public class UpdateThread extends Thread{
3          ……//此处省略了一些定义成员变量和构造器的代码,读者可自行查阅随书附带中的源代码
4          public void run(){
5               while(true){
6                    mv.mBallAndCube.calVertices(count,isBallCube); //计算顶点坐标数据
7                    try{
8                         count++;                             //计数器加1
9                         if(count%mv.mBallAndCube.span==0){   //若达到一轮变化所需步骤
10                             count=0;                        //重置计数器
11                             isBallCube=!isBallCube;         //重置标志位
12                        }
13                        Thread.sleep(40);                    //休眠40毫秒
14                  }catch(Exception e){
15                        e.printStackTrace();
16    }}}

说明

上述UpdateThread类非常简单,主要是在其run方法中定时调用calVertices方法更新顶点坐标数据。同时每次更新后会检查是否达到一轮变化所需的总步骤数,若达到了则将步骤计数器置0,并将变化方向标志位isBallCube(用于表示变化方向是从球到立方体还是立方体到球)置返。

(5)上一步介绍了定时执行BallAndCube类中calVertices方法的线程类——UpdateThread,接下来介绍BallAndCube类中的绘制方法drawSelf,其具体代码如下。

1    public void drawSelf(int texId){
2         MatrixState.rotate(xAngle, 1, 0, 0);                  //绕x轴旋转
3         MatrixState.rotate(yAngle, 0, 1, 0);                  //绕y轴旋转
4         MatrixState.rotate(zAngle, 0, 0, 1);                  //绕z轴旋转
5         GLES30.glUseProgram(mProgram);                        //指定使用某套着色器程序
6         //将最终变换矩阵传入渲染管线
7         GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
8         GLES30.glEnableVertexAttribArray(maTexCoorHandle);      //启用纹理数据数组
9         //绑定到顶点纹理坐标数据缓冲
10        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mTexCoorBufferId);
11        //指定顶点纹理坐标数据使用对应缓冲
12        GLES30.glVertexAttribPointer(maTexCoorHandle, 2, GLES30.GL_FLOAT,false,2*4,0);
13        //启用顶点位置数据数组
14        GLES30.glEnableVertexAttribArray(maPositionHandle);
15        //绑定到顶点位置坐标数据缓冲
16        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mVertexBufferId);
17        //指定顶点位置坐标数据使用对应缓冲
18        GLES30.glVertexAttribPointer(maPositionHandle,  3, GLES30.GL_FLOAT,  false,3*4,0);
19        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);              //激活纹理
20        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);       //绑定纹理
21        synchronized(lock){                       //同步加锁
22            updateMapping(curBallForDraw);        //更新顶点坐标数据缓冲中的顶点数据
23        }
24        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,mIndicesBufferId);//绑定索引缓冲
25        GLES30.glDrawElements(GLES30.GL_TRIANGLES,  //以三角形方式执行绘制
26                 iCount, GLES30.GL_UNSIGNED_INT, 0);
27        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER,0);//绑定到系统默认索引缓冲
28        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0);      //绑定到系统默认数组缓冲
29    }
  • 第2~第4行设置物体绕x、y、z轴旋转指定的角度。
  • 第5~第18行首先指定使用某套着色器程序,然后将最终变换矩阵送入渲染管线,接着绑定了顶点位置、纹理坐标缓冲,并指定顶点位置、纹理坐标使用对应的缓冲,同时还启用了顶点坐标以及纹理坐标数据数组。
  • 第19~第23行激活并绑定了指定的纹理,然后同步加锁后调用updateMapping方法更新顶点数据。这里的同步加锁与前面calVertices方法中是对应的,目的是为了避免更新缓冲中数据的同时数据数组curBallForDraw被其他线程访问。
  • 第24~第28行首先绑定索引缓冲对象,并以三角形方式执行绘制,最后绑定到系统默认的索引与数组缓冲。
时间: 2024-10-15 12:07:27

《OpenGL ES 3.x游戏开发(下卷)》一1.4 映射缓冲区对象的相关文章

《OpenGL ES 3.x游戏开发(下卷)》一导读

前 言 OpenGL ES 3.x游戏开发(下卷) 为什么要写这样一套书 随着智能手机硬件性能的不断提升,如"水果忍者""极品飞车""狂野飙车8:极速凌云"等一批优秀的3D游戏娱乐应用在广大智能机用户间流行开来.与此同时,也带动了手机游戏产业逐渐从2D走向3D.但目前国内专门介绍3D游戏开发的书籍与资料都非常少,同时3D应用开发的门槛又比较高,使得很多初学者无从下手.根据这种情况,笔者结合多年从事3D游戏应用开发的经验编写了这样一套书. 了解一些

《OpenGL ES 3.x游戏开发(下卷)》一1.1 顶点缓冲区

1.1 顶点缓冲区 将绘制物体的顶点数据保存在内存中,在调用glDrawArrays或者glDrawElements等绘制方法前需要调用相应的方法将数据送入显存,I/O开销大,性能不够好. 若采用顶点缓冲区对象存放顶点数据,则不需要在每次绘制前都将顶点数据复制进显存,而是在初始化顶点缓冲区对象时一次性将顶点数据送入显存,每次绘制时直接使用显存中的数据,可以大大提高渲染性能. 1.1.1 基本知识 OpenGL ES 3.0中支持两种类型的顶点缓冲区对象,分别为数组缓冲区对象(Array Buff

《OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例》——6.4节点法向量和面法向量

6.4 点法向量和面法向量 OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例 本章前面几节的案例都是基于球面开发的,球面属于连续.平滑的曲面,因此面上的每个顶点都有确定的法向量.但现实世界中的物体表面并不都是连续.平滑的,此时对于面上的某些点的法向量计算就不那么直观了,图6-18说明了这个问题. 从图6-18中可以看出,顶点A位于长方体左.上.前3个面的交界处,此处是不光滑的.这种情况下顶点A的法向量有两种处理策略,具体如下所列. 在顶点A的位置放置3个不同的顶点,每个顶点看作是仅

《OpenGL ES 3.x游戏开发(上卷)》一第1章 Android概述

第1章 Android概述 OpenGL ES 3.x游戏开发(上卷)随着移动互联网时代的到来,智能手机逐渐走进了人们的生活, Google公司于2007年11月5日发布了基于Linux平台的开源手机操作系统--Android.由于Android系统的开源性以及其他各个方面的因素,其受到了广大手机厂商的青睐,因此需要大量的Android开发人员来满足日益增长的海量软件开发需求. 提示 有些读者可能会有一点奇怪,不是介绍OpenGL ES 3.0的知识吗,怎么一开始就介绍Android呢?这是因为

《OpenGL ES 3.x游戏开发(上卷)》一第2章 游戏开发相关的 Android基础知识

第2章 游戏开发相关的 Android基础知识 OpenGL ES 3.x游戏开发(上卷)虽然本书主要是介绍OpenGL ES 3.0 3D应用及游戏开发的,但由于很多3D游戏应用中还需要用到目标平台的一些其他应用开发方面的知识,而本书3D基础知识部分主要是借助于Android平台来介绍OpenGL ES 3.0 3D应用及游戏开发的.故本章将向读者简要介绍一些在Android游戏开发中必备的一些基础知识,如音效.文件的读取.游戏信息的存储.Socket网络.蓝牙网络等. 提示 由于介绍Andr

OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例》一6.3 定位光与定向光

6.3 定位光与定向光 OpenGL ES 2.0游戏开发(上卷):基础技术和典型案例上一节中介绍的光照效果都是基于定位光光源的,定位光光源类似于现实生活中的白炽灯灯泡,其在某个固定的位置,发出的光向四周发散.定位光照射的一个明显特点就是,在给定光源位置的情况下,对不同位置的物体产生的光照效果不同. 现实世界中并不都是定位光,例如照射到地面上的阳光,光线之间是平行的,这种光称为定向光.定向光照射的明显特点是,在给定光线方向的情况下,场景中不同位置的物体反映出的光照效果完全一致.图6-16中对定位

《OpenGL ES 3.x游戏开发(下卷)》一1.5 其他缓冲区对象操作

1.5 其他缓冲区对象操作 前面已经介绍了顶点缓冲区对象.顶点数组对象.一致缓冲区对象以及映射缓冲区对象,本节将介绍两个其他的缓冲区对象操作,包括复制缓冲区对象(Copying Buffer Objects)以及从颜色缓冲区复制纹理数据(Copying Texture Data from the Color Buffer). 1.5.1 复制缓冲区对象 到目前为止,已经介绍了如何使用glBufferData.glBufferSubData以及 glMapBufferRange方法将数据加载到缓冲

《OpenGL ES 3.x游戏开发(下卷)》一1.8 小结

1.8 小结 本章主要介绍了顶点缓冲区对象.顶点数组对象.一致缓冲区对象.映射缓冲区对象.复制缓冲区对象.从颜色缓冲区复制纹理数据.帧缓冲与渲染缓冲以及多重渲染目标等关于缓冲的基本知识与技术,并给出了一些典型的小案例. 通过本章的学习,读者可以初步领会缓冲区对象所起的重要作用,为以后开发更加复杂.更高性能的3D应用或游戏奠定了良好的基础.

《OpenGL ES 3.x游戏开发(下卷)》一1.2 顶点数组对象

1.2 顶点数组对象 使用了顶点缓冲技术后,绘制效率有了较大的提升.但是还有一点不尽如人意,那就是顶点的位置坐标.法向量.纹理坐标等不同方面的数据每次使用时需要单独指定,重复了一些不必要的工作.OpenGL ES 3.0考虑到了这一点,提供了一种专门用于解决此问题的对象--顶点数组对象(VAO).本节将介绍顶点数组对象. 1.2.1 基本知识与案例效果 顶点数组对象的主要功能就是将绘制一个物体时所需的对应于不同方面(如顶点坐标.法向量.纹理坐标)的顶点缓冲及相关设置包装成一个整体,绘制时直接使用