最近在做 Unitiy 与原生渲染相关的研究学习,对于OpenGL这块自己也是接触不多,有很多的坑需要自己去踩,做的过程中遇到最多的问题是:渲染黑屏 在掘金上搜到这篇文章《OpenGL黑屏及渲染不出来的常见原因总结》很不错,于是记录转载过来,方便日后学习以及问题排查。

原文链接:
https://juejin.cn/post/6844903910742687751

原文

做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线程》

【转载注】这也是我碰到的第一个坑,因为从 Unity 调用到 Android 侧的时候,gl渲染环境没有在一个地方。我花了很大的精力去排查前面shader相关的渲染问题,一直没有注意这个问题,所以浪费了大量时间。我个人认为这个问题需要像使用一门新语言的时候要先确保它能打印出”Hello World”一样,是整个流程的前提。

  • 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 高级进阶:颜色混合》。

作者:程序员kenney

链接:https://juejin.cn/post/6844903910742687751

来源:稀土掘金