文章目录
OpenGL ES 踩坑记录
OpenGL 可以理解为一个状态机管理程序,在效果异常时通常很难有效 debug,在学习过程中或多或少都会碰到各式各样的坑,在此记录下自己遇到的一些问题,避免后续开发中再次踩坑。
常见问题
1. GL_ELEMENT_ARRAY_BUFFER 的正确绑定
glDrawElements()
和 glDrawArrays()
函数都可以绘制三角形,不同的是使用前者可以省去记录很多重复点,且在某些情况下使用前者要远方便于后者。若使用前者则需要对 GL_ELEMENT_ARRAY_BUFFER
进行绑定:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
Learning OpenGL上有这样一段话:
glDrawElements()
函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER
目标的EBO中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO,这还是有点麻烦。不过顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO
绑定时正在绑定的索引缓冲对象会被保存为VAO
的元素缓冲对象。绑定VAO
的同时也会自动绑定EBO
。
上面这段话的意思是在进行绑定 VAO
的过程中,进行 EBO
绑定,此时 EBO
会记录在VAO
中,这样绘制时只需要绑定对应 VAO
即可,而不需要在绑定 EBO
。
但是,这个过程需要注意:
当目标是
GL_ELEMENT_ARRAY_BUFFER
的时候,VAO会储存glBindBuffer()
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
即在解绑 VAO
之前不能解绑 EBO
!!!
正确流程:
void createGLBuffer () {
//···初始化代码···//
// 1. 绑定顶点数组对象
glBindVertexArray(m_VAO_fbo);
// 2. 把的顶点数组复制到顶点缓冲中,供OpenGL使用
lSize = sizeof(float) * vertex_fbo.size();
glBindBuffer(GL_ARRAY_BUFFER, VBO_fbo[0]);
glBufferData(GL_ARRAY_BUFFER, lSize, &vertex_fbo[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
DrawHelper::CheckGLError("createGLBuffer glBufferData");
lSize = sizeof(float) * texCoors.size();
glBindBuffer(GL_ARRAY_BUFFER, VBO_fbo[1]);
glBufferData(GL_ARRAY_BUFFER, lSize, &texCoors[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
DrawHelper::CheckGLError("createGLBuffer glBufferData");
// 3. 复制索引数组到一个索引缓冲中,供OpenGL使用
lSize = sizeof(unsigned int) * index_fbo.size();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO_fbo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, lSize, &index_fbo[0], GL_STATIC_DRAW);
DrawHelper::CheckGLError("createGLBuffer glBufferData");
// 3. 设定顶点属性指针
GLint posAttrib = 0;
glBindBuffer(GL_ARRAY_BUFFER, VBO_fbo[0]);
glEnableVertexAttribArray(posAttrib);
DrawHelper::CheckGLError("createGLBuffer glEnableVertexAttribArray posLocation");
glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
DrawHelper::CheckGLError("createGLBuffer glVertexAttribPointer posLocation");
GLint texCoodsAttrib = 1;
glBindBuffer(GL_ARRAY_BUFFER, VBO_fbo[1]);
glEnableVertexAttribArray(texCoodsAttrib);
DrawHelper::CheckGLError("createGLBuffer glEnableVertexAttribArray texCoodsLocation");
glVertexAttribPointer(texCoodsAttrib, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
DrawHelper::CheckGLError("createGLBuffer glVertexAttribPointer texCoodsLocation");
// 4. 解绑VAO(不是EBO!)
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
glBindVertexArray(GL_NONE);
}
2. 加载顶点数据前确保绑定了 VBO
如果顶点坐标和图像坐标以多个数组的方式保存,可以分组导图顶点坐标和图像坐标,但是需要确保在使用 glEnableVertexAttribArray()
函数前是绑定了对应的 VBO 的,否则绘制时 glDrawArrays()
或者 glDrawElements()
函数可能出现 crash,且检查 GL 状态未必可以检测到这个问题。
GLuint m_VAO;
glGenVertexArrays(1, &m_VAO);
glBindVertexArray(m_VAO);
// 一定要先绑定 m_VBO[0],否则glVertexAttribPointer设置出错
glBindBuffer(GL_ARRAY_BUFFER, m_VBO[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
// 一定要先绑定 m_VBO[1]
glBindBuffer(GL_ARRAY_BUFFER, m_VBO[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
3. sizeof()
计算顶点数组大小
std::vector <float> vertex_fbo{
-1.f, -1.f, 0,
1.f, -1.f, 0,
1.f, 1.f, 0,
-1.f, 1.f, 0
};
int lSize = sizeof(nums);
int lSize2 = sizeof(float) * nums.size();
// lSize = 16 lSize2= 48
上面这段代码运行结果:
nums
是 vector
类对象,该对象内存的是指针,具体需要阅读源码
在OpenGL绘制时正确的是使用 sizeof(float) * nums.size()
计算顶点数组大小;
4. 使用 glGetAttribLocation 获取输入变量 location
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A9ybudNf-1608529250197)(pictures/glGetAttribLocation.png)]
你也可以忽略
layout (location = 0)
标识符,通过在OpenGL代码中使用glGetAttribLocation
查询属性位置值(Location),但是我更喜欢在着色器中设置它们,这样会更容易理解而且节省你(和OpenGL)的工作。
使用中发现,如果省略 layout(location = 0)
,且没有通过 glBindAttribLocation()
分配编号,那个通过 glGetAttribLocation()
获得location可能是负值,导致出错!
5. glDrawArrays()
或 glDrawElements()
crash
通常是由于没有正确绑定 VBO
或 EBO
数据,需要确认上面提到的两个常见问题:
GL_ELEMENT_ARRAY_BUFFER
状态绑定- 绑定
VAO
前确定绑定了VBO
很重要,已经多次碰到!!!
6. FBO 中 glClear()
使用时机
在使用 FBO
绘制测试时发现,第二次绘制的图是在第一次绘制的基础上叠加的,从表现上看就是上一次的帧没有被清除,绘制前已经调用了如下代码:
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.f, 0.f, 0.f, 1.f);
问题就在于这段代码的位置,之前是先调用了这段代码,然后绑定 FBO
,然后绘制,这就导致实际清空的是默认 FBO
,即屏幕显示。因此,只要将这段代码放置到绑定 FBO
之后即可解决问题。
glBindFramebuffer(GL_FRAMEBUFFER, m_FBO);
DrawHelper::CheckGLError("OnDrawFrame glBindFramebuffer");
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.f, 0.f, 0.f, 1.f);
glBindVertexArray(m_VAO);
调试
GL 和 EGL 状态检查
OpenGL (ES) 的使用时一个状态机的控制,如果没有及时检查状态是否异常可能会使得程序运行中产生意想不到的错误,因此十分有必要对状态进行检查。
如下为 GL 和 EGL 状态检查的两个函数,可以直接使用:
static void CheckGLError(const char *TAG)
{
GLenum errorCode = glGetError();
if (GL_NO_ERROR != errorCode) {
std::string errorInfo = "";
switch (errorCode) {
case GL_INVALID_ENUM:
errorInfo = "GL_INVALID_ENUM";
break;
case GL_INVALID_VALUE:
errorInfo = "GL_INVALID_VALUE";
break;
case GL_INVALID_OPERATION:
errorInfo = "GL_INVALID_OPERATION";
break;
case GL_OUT_OF_MEMORY:
errorInfo = "GL_OUT_OF_MEMORY";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
errorInfo = "GL_INVALID_FRAMEBUFFER_OPERATION";
break;
default:
break;
}
LOGE("%s CheckGLError errorCode = %d, errorInfo = %s", TAG, errorCode, errorInfo.c_str());
}
}
static void CheckGLError(const char * file, int line, const char *TAG)
{
GLenum errorCode = glGetError();
if (GL_NO_ERROR != errorCode) {
std::string errorInfo = "";
switch (errorCode) {
case GL_INVALID_ENUM:
errorInfo = "GL_INVALID_ENUM";
break;
case GL_INVALID_VALUE:
errorInfo = "GL_INVALID_VALUE";
break;
case GL_INVALID_OPERATION:
errorInfo = "GL_INVALID_OPERATION";
break;
case GL_OUT_OF_MEMORY:
errorInfo = "GL_OUT_OF_MEMORY";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION:
errorInfo = "GL_INVALID_FRAMEBUFFER_OPERATION";
break;
default:
break;
}
LOGE("file: %s, line: %d, %s CheckGLError errorCode = %d, errorInfo = %s", file, line,
TAG, errorCode, errorInfo.c_str());
}
}