OpenGL黑屏及渲染不出来的常见原因总结

12,970 阅读7分钟

大家好,我是程序员kenney,今天给大家分析一下OpenGL的黑屏及渲染不出来常见原因。

OpenGL开发的同学,想必一定碰到过黑屏的问题,特别是刚接触OpenGL的同学,可能会觉得黑屏问题让人相当头疼,因为OpenGL的查错没有一般编程时那么简单,我们通常是利用glGetError()这个API来获取错误码,但这个方法获取的错误是调用这个方法时,已经产生的错误,它有可能是很久之前产生的,这样查越来还是比较不方便的,而且,有些黑屏以及渲染不出来的情况下,glGetError()也不会报任何错。

在给大家总结常见的黑屏原因之前,我们先来铺垫一下基础知识,其实屏幕也是一块frame buffer,但它比较特殊,是0frame buffer,我们如果自己申请frame buffer的话,得到的id是大于0的。那么frame buffer它就会有自己的颜色,如果不特意设置的话,它就是黑色的,因此如果我们渲染操作未正确执行,什么也没渲染出来,自然看到了底色的黑色。

我们也可以通过glClearColor()+glClear()来设置消除颜色及执行消除操作,来将一个frame buffer清成某种颜色。因此,如果你将frame buffer清成了别的颜色,但其它渲染操作未正确执行,你有可能也不是黑屏,而是你设置的消除颜色,这里也一并总结了,统成为黑屏,同时也包括其它一些不正确的情形。

如果不是渲染到屏幕上,是渲染到一个离屏的frame buffer上,同样也会遇到各种黑掉或者渲染不出来的情况,有些原因会同时导致上屏和离屏都黑,有些只影响其中一种情况。

下面给大家总结一下:

  • 调用线程的Context不正确

    OpenGLAPI在调用时需要有正确的上下文,在Android中称为EGL ContextIOS中是EAGL Context,其它平台有其它平台的叫法,但原理类似。一个线程需要跟EGL Context绑定才能正确使用OpenGLAPI,否则调用不会有任何效果,具体可参考我的一篇文章:《OpenGL ES 高级进阶:EGL及GL线程》

  • GL Program不正确

    OpenGL渲染需要通过GL Program,它就是一个程序,和我们的普通程序是一个道理,只不过它是运行在GPU上的,如果它不正确了,那自然就渲染不出正确的结果,常见的不正确原因为shader编译失败,通常是因为语法错误,可以用glGetShaderInfoLog()来在编译之后查看相关shader信息,以及在Link后用glGetProgramInfoLog()查看相关program信息,如果得到的信息为空,则说明没有错。

  • 没有use program

    渲染前需要通过glUseProgran设置本次渲染所用的program,如果未设置则无法执行到对应的shader,自然无法渲染出来。

  • 未调用glDrawXXX()

    要渲染出来东西,必须调用glDrawXXX(),一般很少出现没调的情况,一般都是低级失误,最好也排查一下。

  • 对于底层是多buffer实现的surface,渲染后未进行swap buffer

    常见的是双buffer,此时有一个back buffer和一个front bufferfront buffer是正在显存的这个,back buffer是正在渲染的,如果draw call后没有swap buffer,那back buffer不会呈现出来,因此渲染不出来,这里是特定上屏,如果渲染不是要上屏,则无需考虑这个问题。

  • frame bufferattachment不正确

    在离屏渲染情况下,当我们要渲染到一个frame buffer上,这个frame buffer必须正确绑定了attachment,否则相当于frame buffer是个空壳,它没有任何可用于承载渲染结果的空间。

  • 顶点attribute值设置错误

    顶点关系到渲染到什么位置,如果设置错误导致渲染的位置在可视范围之外,那么就看不到了,这里的范围是什么呢?如果直接用NDC坐标渲染,那就是-1~1,如果是用世界坐标来渲染,那就要看具体设置的投影矩阵,详细原理可参考我的另一篇文章《OpenGL 3D渲染技术:坐标系及矩阵变换》

  • attribute未启用

    我们通过想要设置一个attribute的值,需要获取这个attributelocation,并通过glVertexAttribPointer()给它设置值,但别忘了需要使用glGetAttribLocation()来启用这个location,不然设置了也没有用,默认是不启用的。

  • VAO/VBO未绑定或者绑定错误

    如果是用VAO/VBO的方式渲染,在渲染前要绑定正确的VAO/VBO,否则等于没指定或者指定错了顶点,就渲染不出来了。

  • VAO/VBO方式渲染之后未重置,后面接着用非VAO/VBO方式渲染

    在用VAO/VBO方式渲染之后如果未重置,那么顶点绑定的还是VAO/VBO指定的顶点,此时如果再用普通的glVertexAttribPointer()的方式指定顶点渲染,那用法上会冲突,因为VAO/VBO的方式要求glVertexAttribPointer()函数不指定顶点数据,而普通用法中glVertexAttribPointer()又要指定顶点数据,此时容易造成顶点混乱,渲染结果不正确。

  • View Port设置错误

    View Port即视口,可以理解成我们通过一个窗口去看见OpenGL世界坐标系里渲染的景物,就像我们通过窗口看到室外的景物一样,如果这个窗口没设置或者设置不正确,也会导致看不到东西,一般情况下,我们会将它设置为surface的大小,这样渲染出来的东西就刚好填满这个surface

  • 没有渲染到0frame buffer

    有时候渲染操作有很多步,想做完这些步骤后,再将做好的结果显示的屏幕上,这时就会用一些frame buffer来做离屏渲染,但在最后一步渲染到屏幕上时,需要将frame buffer绑定回0号,才能上屏。

  • 渲染了一个不正确的纹理

    例如我们希望对一个纹理做一些处理然后渲染出来,但如果这个纹理本身是不正确的,例如前面的步骤出了一些错,导致给过来的纹理id不正确,比如是0,或者纹理id是正确的,但这个纹理是全黑的或者空的,也会导致黑屏。

  • glDrawXXX()方法传递的顶点数不正确

    我们在调用glDrawXXX(),会设置顶点数组的开始位置和数量,如果设置不正确,导致传递的顶点是0个,也会导致渲染不出来任何东西。

  • 顶点bufferposition不正确

    这一点主要是针对javakotlinglVertexAttribPointer()接受数据时是通过一个buffer,而我们往bufferput数据后,bufferposition会相应地往后移动,因此在调用glVertexAttribPointer()之前,记得将position设回到0,否则它将从末尾开始取数据,当然就取不到了。

  • 面剔除的原因

    如果开启了cull face,那么会按你指定的cull方式来剔除指定顶点旋转顺序的三角面片,如果视线方向看过去的刚好被剔除了,自然就看不见了。

  • 未开启颜色混合渲染了有透明度的纹理

    OpenGL默认是不开启颜色混合的,这会导致透明的部分通常会被渲染成黑色,而不是透出下面的颜色,具体可以参数我的一篇文章:《OpenGL ES 高级进阶:颜色混合》

好了,先总结到这,这些是比较常见的,也欢迎大家给我留意补充讨论~

感谢阅读!