标签(空格分隔): OpenGL-ES
1 2 3 | 版本:1 作者:陈小默 声明:禁止商用,禁止转载 |
该文章仅发布于作业部落,简书
上一篇:NDK开发OpenGL ES 3.0(三)——着色器基础
如遇MarkDown支持不完整的问题 请点击这里查看原文
参考书目:
[1]Donald Hearn,M.Pauline Barker.计算机图形学 第四版(蔡士杰 译).北京:电子工业出版社
[2]Dave Shreiner,Graham Sellers.OpenGL编程指南 第八版(王锐 译).北京:机械工业出版社
[3]Dan Ginsburg,Budirjanto Purnomo.OpenGL ES 3.0 编程指南 第二版(姚军 译).北京:机械工业出版社
参考示例:
[1]GoogleSamples - Android NDK
本章通过旋转的彩色方块的例子来介绍OpenGL中的顶点数组、顶点属相和缓冲区对象的相关概念。示例改编自谷歌官方Demo。
最终效果图
[toc]
七、会旋转的彩色方块
7.1 目录结构
图7.1-1 目录结构
7.2 定义本地方法
这里的过程跟第二章没有区别,以下展示详细代码不再详解
在CMakeList.txt文件夹添加如下内容
1 2 3 4 5 6 7 8 9 10 11 | add_library( rect-lib SHARED src/main/cpp/esUtil.cpp src/main/cpp/rect-lib.cpp) target_link_libraries(rect-lib ${OPENGL_LIB} android EGL log m) |
创建Java文件
1 2 3 4 5 6 7 8 9 | public class RectLib { static { System.loadLibrary("rect-lib"); } public static native boolean init(); public static native void resize(int width,int height); public static native void step(); public static native void destroy(); } |
创建一个RectView文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class RectView(context: Context) : GLSurfaceView(context){ init { setEGLConfigChooser(8,8,8,0,16,0) setEGLContextClientVersion(3) setRenderer(RectRender()) } override fun onPause() { RectLib.destroy() super.onPause() } class RectRender:GLSurfaceView.Renderer{ override fun onDrawFrame(gl: GL10?) { RectLib.step() } override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { RectLib.resize(width, height) } override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { RectLib.init() } } } |
创建一个KotlinActivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class RectActivity : AppCompatActivity() { lateinit var view: GLSurfaceView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) view = RectView(this) setContentView(view) } override fun onResume() { super.onResume() view.onResume() } override fun onPause() { super.onPause() view.onPause() } } |
在MainActivity的布局中添加一个按钮
1 2 3 4 5 | <Button android:text="绘制一个旋转的矩形" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/btn_drawRectangle" /> |
在MainActivity中添加点击事件
1 2 3 4 | btn_drawRectangle.setOnClickListener { val intent = Intent(this,RectActivity::class.java) startActivity(intent) } |
7.3 实现本地方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | #define LOG_TAG "rect-lib" #include "esUtil.h" #include <math.h> #include <time.h> #define STR(s) #s #define STRV(s) STR(s) #define POS_ATTRIB 0 #define COLOR_ATTRIB 1 #define SCALEROT_ATTRIB 2 #define OFFSET_ATTRIB 3 #define TWO_PI (2.0 * M_PI) #define MAX_ROT_SPEED (0.3 * TWO_PI) static const char VERTEX_SHADER[] = "#version 300 es\n" "layout(location = " STRV(POS_ATTRIB) ") in vec2 pos;\n" "layout(location=" STRV(COLOR_ATTRIB) ") in vec4 color;\n" "layout(location=" STRV(SCALEROT_ATTRIB) ") in vec4 scaleRot;\n" "layout(location=" STRV(OFFSET_ATTRIB) ") in vec2 offset;\n" "out vec4 vColor;\n" "void main() {\n" " mat2 sr = mat2(scaleRot.xy, scaleRot.zw);\n" " gl_Position = vec4(sr*pos + offset, 0.0, 1.0);\n" " vColor = color;\n" "}\n"; static const char FRAGMENT_SHADER[] = "#version 300 es\n" "precision mediump float;\n" "in vec4 vColor;\n" "out vec4 outColor;\n" "void main() {\n" " outColor = vColor;\n" "}\n"; struct Vertex { GLfloat pos[2]; GLubyte rgba[4]; }; const Vertex QUAD[4] = { {{-0.7f, -0.7f}, {0x00, 0xFF, 0x00}}, {{0.7f, -0.7f}, {0x00, 0x00, 0xFF}}, {{-0.7f, 0.7f}, {0xFF, 0x00, 0x00}}, {{0.7f, 0.7f}, {0xFF, 0xFF, 0xFF}}, }; class Renderer { public: bool init(); Renderer(); ~Renderer(); void resize(int w, int h); void render(); private: enum { VB_INSTANCE, VB_SCALEROT, VB_OFFSET, VB_COUNT }; float mScale[2]; float mAngularVelocity; uint64_t mLastFrameNs; float mAngles; GLuint mProgram; GLuint mVB[VB_COUNT]; GLuint mVBState; float *mapTransformBuf(); void unmapTransformBuf(); void draw(); void calcSceneParams(int w, int h); void step(); }; Renderer::Renderer() : mLastFrameNs(0), mProgram(0), mVBState(0) { memset(mScale, 0, sizeof(mScale)); for (int i = 0; i < VB_COUNT; i++) mVB[i] = 0; } Renderer::~Renderer() { glDeleteVertexArrays(1, &mVBState); glDeleteBuffers(VB_COUNT, mVB); glDeleteProgram(mProgram); } bool Renderer::init() { mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); if (!mProgram) return false; glGenBuffers(VB_COUNT, mVB); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_INSTANCE]); glBufferData(GL_ARRAY_BUFFER, sizeof(QUAD), &QUAD[0], GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_SCALEROT]); glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(float), NULL, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_OFFSET]); glBufferData(GL_ARRAY_BUFFER, 2 * sizeof(float), NULL, GL_STATIC_DRAW); glGenVertexArrays(1, &mVBState); glBindVertexArray(mVBState); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_INSTANCE]); glVertexAttribPointer(POS_ATTRIB, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, pos)); glVertexAttribPointer(COLOR_ATTRIB, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, rgba)); glEnableVertexAttribArray(POS_ATTRIB); glEnableVertexAttribArray(COLOR_ATTRIB); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_SCALEROT]); glVertexAttribPointer(SCALEROT_ATTRIB, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); glEnableVertexAttribArray(SCALEROT_ATTRIB); glVertexAttribDivisor(SCALEROT_ATTRIB, 1); glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_OFFSET]); glVertexAttribPointer(OFFSET_ATTRIB, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0); glEnableVertexAttribArray(OFFSET_ATTRIB); glVertexAttribDivisor(OFFSET_ATTRIB, 1); return true; } void Renderer::resize(int w, int h) { calcSceneParams(w, h); mAngles = float(drand48() * TWO_PI); mAngularVelocity = float(MAX_ROT_SPEED * (2.0 * drand48() - 1.0)); mLastFrameNs = 0; glViewport(0, 0, w, h); } void Renderer::calcSceneParams(int w, int h) { const float CELL_SIZE = 0.5f; const float scene2clip[2] = {1.0f, fmaxf(w, h) / fminf(w, h)}; int major = w >= h ? 0 : 1; int minor = w >= h ? 1 : 0; mScale[major] = CELL_SIZE * scene2clip[0]; mScale[minor] = CELL_SIZE * scene2clip[1]; } void Renderer::step() { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); auto nowNs = now.tv_sec * 1000000000ull + now.tv_nsec; if (mLastFrameNs > 0) { float dt = float(nowNs - mLastFrameNs) * 0.000000001f; mAngles += mAngularVelocity * dt; if (mAngles >= TWO_PI) { mAngles -= TWO_PI; } else if (mAngles <= -TWO_PI) { mAngles += TWO_PI; } float *transforms = mapTransformBuf(); float s = sinf(mAngles); float c = cosf(mAngles); transforms[0] = c * mScale[0]; transforms[1] = s * mScale[1]; transforms[2] = -s * mScale[0]; transforms[3] = c * mScale[1]; } unmapTransformBuf(); mLastFrameNs = nowNs; } void Renderer::render() { step(); glClearColor(0.2f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); draw(); checkGlError("Renderer::render"); } float *Renderer::mapTransformBuf() { glBindBuffer(GL_ARRAY_BUFFER, mVB[VB_SCALEROT]); return (float *) glMapBufferRange(GL_ARRAY_BUFFER, 0, 4 * sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); } void Renderer::unmapTransformBuf() { glUnmapBuffer(GL_ARRAY_BUFFER); } void Renderer::draw() { glUseProgram(mProgram); glBindVertexArray(mVBState); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 1); } Renderer *createRenderer() { Renderer *renderer = new Renderer; if (!renderer->init()) { delete renderer; return NULL; } return renderer; } static Renderer *g_renderer = NULL; extern "C" { JNIEXPORT jboolean JNICALL Java_com_github_cccxm_gles_model_RectLib_init(JNIEnv *env, jclass type); JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_RectLib_resize(JNIEnv *env, jclass type, jint width, jint height); JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_RectLib_step(JNIEnv *env, jclass type); JNIEXPORT void JNICALL Java_com_github_cccxm_gles_model_RectLib_destroy(JNIEnv *env, jclass type); }; jboolean Java_com_github_cccxm_gles_model_RectLib_init(JNIEnv *env, jclass type) { if (g_renderer) { delete g_renderer; g_renderer = NULL; } g_renderer = createRenderer(); if (!g_renderer) return JNI_FALSE; return JNI_TRUE; } void Java_com_github_cccxm_gles_model_RectLib_resize(JNIEnv *env, jclass type, jint width, jint height) { if (g_renderer) { g_renderer->resize(width, height); } } void Java_com_github_cccxm_gles_model_RectLib_step(JNIEnv *env, jclass type) { if (g_renderer) { g_renderer->render(); } } void Java_com_github_cccxm_gles_model_RectLib_destroy(JNIEnv *env, jclass type) { delete g_renderer; g_renderer = NULL; } #undef LOG_TAG |
7.4 着色器解析
7.4.1 顶点着色器解析
上面程序使用的顶点着色器代码如下:
1 2 3 4 5 6 7 8 9 10 11 | #version 300 es layout(location = 0) in vec2 pos; layout(location = 1) in vec4 color; layout(location = 2) in vec4 scaleRot; layout(location = 3) in vec2 offset; out vec4 vColor; void main() { mat2 sr = mat2(scaleRot.xy, scaleRot.zw); gl_Position = vec4(sr*pos + offset, 0.0, 1.0); vColor = color; } |
程序的第一行是版本信息,提示编译器如何编译这个着色器。
1 | layout(location = 0) in vec2 pos; |
上述语句in关键字表明了这个参数是从外部获取的(可能来自于用户程序,也可能来自于上一个着色器),而之前的布局限定符layout(location = 0)则是定位该参数的来源,表明这个参数被存放在坐标(索引)为0的布局对象中。
1 | out vec4 vColor; |
上述语句表示参数vColor是输出参数,其值将会被交给下一个着色器。
1 | mat2 sr = mat2(scaleRot.xy, scaleRot.zw); |
上述语句生成了一个2X2的矩阵,其第一列的值来源于scaleRot的xy分量,第二列的值来源于scaleRot的zw分量。
1 | gl_Position = vec4(sr*pos + offset, 0.0, 1.0); |
上述语句中gl_Position是一个全局变量,指代当前正在进行计算的数据的坐标,这里生成了一个4维向量,向量的前两个维度来源于2X2矩阵sr和之前的位置相乘生成的新矩阵与向量offset相加的结果。这一步的作用是计算新位置以达到图形旋转的目的。以下为坐标旋转方程:
$$P'=R·P \tag{7.4.1-1}$$
其中$P'$为旋转后的坐标位置$P$为原始坐标,旋转矩阵
$$R=\begin{bmatrix}
cos\theta & -sin\theta \
sin\theta & cos\theta \
\end{bmatrix} \tag{7.4.1-2}$$
矩阵相乘的计算公式为:对于乘法$C=A B$ 有
$$c_{ij}=\sum_{k=1}^n a_{ik}b_{kj} \tag{7.4.1-3}$$
1 | vColor = color; |
上述语句表示,对收到的颜色值不做处理直接发送到下一个着色阶段(对于本程序就是发送给片元着色阶段)。
7.4.2 片元着色器解析
本例使用的片元着色器代码如下:
1 2 3 4 5 6 7 | #version 300 es precision mediump float; in vec4 vColor; out vec4 outColor; void main() { outColor = vColor; } |
该着色器代码的作用就是将收到的颜色值输出,用于后续渲染。
八、顶点属性、顶点数组和缓冲区对象
8.1 顶点属性和顶点数组
顶点数组中包含了每一个顶点的属性,是保存在用户内核的缓冲区。(语文不好的跟我念:顶点数组是缓冲区,这个缓冲区在用户内核,里面存放的是每一个顶点的属性)。
8.1.3 单个顶点属性
如果我们有单个的顶点需要处理的话,可以使用下列方法将一个顶点对象映射为一个索引,这样我们就可以在需要的地方使用索引而不是顶点对象去操作数据。OpenGL提供了下列方法去创建单个顶点对象。
void glVertexAttrib1f (GLuint index, GLfloat x);
void glVertexAttrib1fv (GLuint index, const GLfloat *v);
void glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y);
void glVertexAttrib2fv (GLuint index, const GLfloat *v);
void glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z);
void glVertexAttrib3fv (GLuint index, const GLfloat *v);
void glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
void glVertexAttrib4fv (GLuint index, const GLfloat *v);
8.1.2 顶点数组
void glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
void glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer);
前一个方法允许任何数字类型数组,后一个方法只允许整型数组。
- index:顶点索引,可由用户自行指定。
- size:对于坐标来说不应该使用size(尺度)最贴切的是dimension(维度),取值范围1~4,代表当前顶点使用的坐标系,是线性坐标系(只有x),或是4维空间坐标系(x,y,z,w)。
- type:数据格式,包括常用的数据类型,比如GL_FLOAT等。
- normalized:是否规范化,对于使用glVertexAttribPointer方法的非GL_FLOAT数组,将次标记设置为真后,有符号数将被按比例映射到GL_FLOAT[-1.0,1.0]区间,无符号数将被按比例映射到[0,1.0]区间。
- stride:偏移量,如果该数组不仅仅用于存放坐标的话,需要说明偏移量,否则设置为0即可。
- ptr:用作缓冲区的数组
8.1.3 启用和关闭顶点数组
如果我们使用
void glEnableVertexAttribArray (GLuint index);
void glDisableVertexAttribArray (GLuint index);
8.1.4 布局限定符
在7.4.1的分析中可以看出,一旦我们将一个索引与顶点数组绑定起来,我们就可以在着色器中自由的使用数组中的数据。这种方式使用起来最为方便,但是在某些情况下,我们可能还需要另外的方式(值得注意的是,这种绑定在下一次程序链接时生效)
void glBindAttribLocation (GLuint program, GLuint index, const GLchar *name);
- program:程序对象索引。
- index:定点数组索引。
- name:着色器中使用此数据的变量的名称。
例如:我们可以使用此方法修改7.4.1中的着色器
1 | glBindAttribLocation(program,0,"pos");//用户程序添加绑定 |
1 | in vec2 pos;//不需要布局限定符 |
8.2 顶点数组对象
使用顶点数组操作可能需要多次调用8.1中的方法来切换状态。实际上在OpenGL ES 3.0 中,同一时刻总是只有一个活动的顶点数组对象。那么我们可以通过更简单的方法去指定当前正在使用的顶点数组。使用方式就是记录一段你需要对该数组进行的操作,并保存到一个操作对象当中,然后再每次需要时调用此操作记录对象即可。
8.2.1 创建顶点数组对象
void glGenVertexArrays (GLsizei n, GLuint *arrays);
- n:需要的顶点数组对象个数
- arrays:保存对象索引的数组
8.2.2 绑定顶点数组对象
顶点数组对象的目的是简化操作,我们需要将一个数组对象和一系列关于顶点数组和缓存的操作绑定起来。当我们调用完毕下列方法的时候,其后所有关于顶点数组和缓存的操作都将被记录到顶点数组对象的状态当中。
void glBindVertexArray (GLuint array);
- array:需要使用或修改状态的顶点数组对象的索引。
8.2.3 删除顶点数组对象
同理,当我们不再需要对一段顶点数组进行操作的时候,需要通知OpenGL删除关于此数组的操作记录。我们可以使用如下方法:
void glDeleteVertexArrays (GLsizei n, const GLuint *arrays);
- n:需要删除的对象个数。
- arrays:包含数组对象索引的数组。
8.3 顶点缓冲区对象
假如我们将数据存储到用户内存,那么每当我们调用绘图方法时,都需要将数据从用户内存复制到图形内存中。但是我们没有必要再每次绘图时都去复制数据。为了节省内存和电力开销,OpenGL ES 提供了顶点缓存技术。OpenGL ES 3.0支持两类缓存区对象:
- GL_ARRAY_BUFFER:标志指定缓冲区用于保存顶点数据
- GL_ELEMENT_ARRAY_BUFFER:标志指定缓冲区用于保存图元索引数据
8.3.1 获取缓冲区索引
由于这个缓冲区需要在两个内核空间中共享,所以OpenGL不建议像创建顶点数组那样让用户自己去指定索引。OpenGL提供了一个函数用来获取当前空闲的缓冲区索引。
void glGenBuffers (GLsizei n, GLuint *buffers);
- n:需要得到的索引数量。
- buffers:保存索引的数组。
8.3.2 绑定缓冲区
我们通过8.3.1获取到了可用的缓冲区索引,接下来我们需要将索引与缓冲区类型进行绑定。
void glBindBuffer (GLenum target, GLuint buffer);
- target:缓冲区存放的数据类型。
- buffer:已获取的缓冲区索引。
8.3.3 绑定数据
如果我们通过8.3.2绑定了缓冲区索引,接下来就可以将数据绑定到缓冲区了。
void glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
- target:缓冲区存放的数据结构。
- size:缓冲区大小,单位:字节。
- data:数据源,其内容将会被复制到缓冲区中。
- usage:缓冲区的使用方法。
使用方式主要分为以下三类:
只读型(一次修改多次访问):
- GL_STATIC_DRAW
- GL_STATIC_READ
- GL_STATIC_COPY
读写型(多次修改多次访问):
- GL_DYNAMIC_DRAW
- GL_DYNAMIC_READ
- GL_DYNAMIC_COPY
数据流型(一次修改一次访问):
- GL_STREAM_DREW
- GL_STREAM_READ
- GL_STREAM_COPY
这三种类型的后缀DRAW表示数据将被送往GPU渲染、READ表述数据会回传给用户程序、COPY表示其同时具有前两种性质。
该方法中的data可以为NULL,如果传入NULL则表示延迟初始化。那么我们应该使用什么方法去再次加载数据呢?接下来介绍一个可以初始化和更新数据的函数:
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
- offset:缓冲区待修改数据偏移量。
- size:被修改的数据存储字节数。
- data:数据源。
8.3.4 删除缓冲区
当我们使用完缓冲区后,应当命令OpenGL销毁这个区域:
void glDeleteBuffers (GLsizei n, const GLuint *buffers);
- n:要删除的缓冲区个数。
- buffers:包含缓冲区索引的数组。
8.4 映射缓冲区对象
当我们需要操作保存在缓冲区的数据时会发现,仅通过8.4节的方式是不能高效的满足需求的。因为我们需要在程序中保存一个引用并不断的调用
8.4.1 获取缓冲区对象指针
我们可以通过以下指令获取指向所有或部分缓存的指针,如果出现错误,该函数将返回NULL。
void* glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
- target:缓冲区存放的数据结构。
- offset:缓冲区数据存储的偏移量,单位:字节。
- length:需要的缓冲区字节数。
- access:访问方式,是如下方式的一种或组合。
缓冲区对象的访问方式:
必须包含的选项:
- GL_MAP_READ_BIT:只读
- GL_MAP_WRITE_BIT:读写
可选的选项:
- GL_MAP_INVALIDATE_RANGE:告诉OpenGL之前该范围内的数据可能被丢弃。这个标志不得与GL_MAP_READ_BIT结合使用。
- GL_MAP_INVALIDATE_BUFFER_BIT:告诉OpenGL之前所有的数据都可能被丢弃。这个标志不得与GL_MAP_READ_BIT结合使用。
- GL_MAP_FLUSH_EXPLICIT_BIT:这个标志只能与GL_MAP_WRITE_BIT一起使用。当使用了此标志之后,需要应用程序明确的使用glFlushMappedBufferRange函数刷新操作。在没有使用此标识位的情况下,glUnmapBuffer函数被调用时会自动刷新。
- GL_MAP_UNSYNCHRONIZED_BIT:告诉OpenGL不要同步等待glMapBufferRange之前的操作,也就是说指定了该标识之后,会立即从缓冲区中获取引用,无论前面是否还有其他对缓冲区的操作。
8.4.2 取消缓冲区映射
当我们不再使用缓冲区映射时,需要使用如下函数解除当前正在使用的映射关系
GLboolean glUnmapBuffer (GLenum target);
- target:缓冲区存放的数据结构。
8.4.3 刷新缓冲区
如果我们希望对映射的修改能够立即同步到缓冲区,可以使用glUnmapBuffer解除映射,然后映射中的数据就会被自动刷新到缓冲区中。但是这种方式缺点明显,比如对于连续刷新来说操作繁琐,而且,假如只修改一小部分数据,这种方式却刷新了整个缓冲区。OpenGL ES 3.0提供了部分刷新的方法
void glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length);
- target:缓冲区存放的数据结构。
- offset:缓冲区数据存储的偏移量,单位:字节。
- length:从偏移点开始刷新的长度。
8.4.4 复制缓冲区对象
OpenGL提供了如下所示的函数来在两个缓冲区之间复制数据
void glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
- readTarget:源缓冲区存放的数据结构。
- writeTarget:目标缓冲区存放的数据结构。
- readOffset:源缓冲区偏移量。
- writeOffset:目标缓冲区偏移量。
- size:需要复制的字节长度。
下一篇:NDK开发OpenGL ES 3.0(五)——一个立方体