大家好,我是程序员kenney,今天给大家分析一下OpenGL
的黑屏及渲染不出来常见原因。
做OpenGL
开发的同学,想必一定碰到过黑屏的问题,特别是刚接触OpenGL
的同学,可能会觉得黑屏问题让人相当头疼,因为OpenGL
的查错没有一般编程时那么简单,我们通常是利用glGetError()
这个API
来获取错误码,但这个方法获取的错误是调用这个方法时,已经产生的错误,它有可能是很久之前产生的,这样查越来还是比较不方便的,而且,有些黑屏以及渲染不出来的情况下,glGetError()
也不会报任何错。
在给大家总结常见的黑屏原因之前,我们先来铺垫一下基础知识,其实屏幕也是一块frame buffer
,但它比较特殊,是0
号frame buffer
,我们如果自己申请frame buffer
的话,得到的id
是大于0的。那么frame buffer
它就会有自己的颜色,如果不特意设置的话,它就是黑色的,因此如果我们渲染操作未正确执行,什么也没渲染出来,自然看到了底色的黑色。
我们也可以通过glClearColor()+glClear()
来设置消除颜色及执行消除操作,来将一个frame buffer
清成某种颜色。因此,如果你将frame buffer
清成了别的颜色,但其它渲染操作未正确执行,你有可能也不是黑屏,而是你设置的消除颜色,这里也一并总结了,统成为黑屏,同时也包括其它一些不正确的情形。
如果不是渲染到屏幕上,是渲染到一个离屏的frame buffer
上,同样也会遇到各种黑掉或者渲染不出来的情况,有些原因会同时导致上屏和离屏都黑,有些只影响其中一种情况。
下面给大家总结一下:
-
调用线程的Context不正确
OpenGL
的API
在调用时需要有正确的上下文,在Android
中称为EGL Context
,IOS
中是EAGL Context
,其它平台有其它平台的叫法,但原理类似。一个线程需要跟EGL Context
绑定才能正确使用OpenGL
的API
,否则调用不会有任何效果,具体可参考我的一篇文章:《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 buffer
,front buffer
是正在显存的这个,back buffer
是正在渲染的,如果draw call
后没有swap buffer
,那back buffer
不会呈现出来,因此渲染不出来,这里是特定上屏,如果渲染不是要上屏,则无需考虑这个问题。 -
frame buffer
的attachment
不正确在离屏渲染情况下,当我们要渲染到一个
frame buffer
上,这个frame buffer
必须正确绑定了attachment
,否则相当于frame buffer
是个空壳,它没有任何可用于承载渲染结果的空间。 -
顶点
attribute
值设置错误顶点关系到渲染到什么位置,如果设置错误导致渲染的位置在可视范围之外,那么就看不到了,这里的范围是什么呢?如果直接用
NDC
坐标渲染,那就是-1~1
,如果是用世界坐标来渲染,那就要看具体设置的投影矩阵,详细原理可参考我的另一篇文章《OpenGL 3D渲染技术:坐标系及矩阵变换》。 -
attribute
未启用我们通过想要设置一个
attribute
的值,需要获取这个attribute
的location
,并通过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
。 -
没有渲染到
0
号frame buffer
有时候渲染操作有很多步,想做完这些步骤后,再将做好的结果显示的屏幕上,这时就会用一些
frame buffer
来做离屏渲染,但在最后一步渲染到屏幕上时,需要将frame buffer
绑定回0
号,才能上屏。 -
渲染了一个不正确的纹理
例如我们希望对一个纹理做一些处理然后渲染出来,但如果这个纹理本身是不正确的,例如前面的步骤出了一些错,导致给过来的纹理
id
不正确,比如是0
,或者纹理id
是正确的,但这个纹理是全黑的或者空的,也会导致黑屏。 -
glDrawXXX()
方法传递的顶点数不正确我们在调用
glDrawXXX()
,会设置顶点数组的开始位置和数量,如果设置不正确,导致传递的顶点是0
个,也会导致渲染不出来任何东西。 -
顶点
buffer
的position
不正确这一点主要是针对
java
及kotlin
,glVertexAttribPointer()
接受数据时是通过一个buffer
,而我们往buffer
是put
数据后,buffer
的position
会相应地往后移动,因此在调用glVertexAttribPointer()
之前,记得将position
设回到0
,否则它将从末尾开始取数据,当然就取不到了。 -
面剔除的原因
如果开启了
cull face
,那么会按你指定的cull
方式来剔除指定顶点旋转顺序的三角面片,如果视线方向看过去的刚好被剔除了,自然就看不见了。 -
未开启颜色混合渲染了有透明度的纹理
OpenGL
默认是不开启颜色混合的,这会导致透明的部分通常会被渲染成黑色,而不是透出下面的颜色,具体可以参数我的一篇文章:《OpenGL ES 高级进阶:颜色混合》。
好了,先总结到这,这些是比较常见的,也欢迎大家给我留意补充讨论~
感谢阅读!