C++,OpenGL Z-buffer预处理

8

我正在制作一个简单的像素引擎(类似于Minecraft),目前正处于消除遮挡面以获得宝贵帧率的阶段。我的OpenGL经验不是很丰富,也不太理解glColorMask函数的原理。

以下是我的代码:

// new and shiny
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// this one goes without saying
glEnable(GL_DEPTH_TEST);

// I want to see my code working, so fill the mask
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

// fill the z-buffer, or whatever
glDepthFunc(GL_LESS);
glColorMask(0,0,0,0);
glDepthMask(GL_TRUE);

// do a first draw pass
world_display();

// now only show lines, so I can see the occluded lines do not display
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

// I guess the error is somewhere here
glDepthFunc(GL_LEQUAL);
glColorMask(1,1,1,1);
glDepthMask(GL_FALSE);

// do a second draw pass for the real rendering
world_display();

这个方法有些作用,但是一旦我改变相机位置,世界就开始逐渐消失了,我看到的线条越来越少,直到完全没有。


1
回复:关于宝贵的FPS,world_display()函数是否已经使用了顶点数组(VAs)或者顶点缓冲对象(VBOs)来提交几何图形呢? - genpfault
1
+1 给 @genpfault。顺便问一下,你能否发布一些屏幕截图来展示你的情况?除非我漏看了什么明显的问题,否则错误可能出现在其他地方 - 上面的代码看起来没问题。 - Kos
3个回答

10

听起来你没有清除深度缓冲区。

在尝试使用 glClear 清除深度缓冲区时,需要启用深度写入(通过 glDepthMask(GL_TRUE);)。你可能仍然从上一帧禁用了它,导致在后续帧中所有的清除操作都无效。只需将你的 glDepthMask 调用移动到 glClear 之前即可。


2
噢,那很可能就是原因了,发现得好。看吧孩子们:这就是为什么你总是在操作之前设置所有OpenGL状态所需的状态。 - datenwolf
我现在感觉很愚蠢。不幸的是,它减半了我的帧率,而不是加倍,该死! - Solenoid
2
是的。只有在填充率受限(并且如果您有非常昂贵的每像素操作)时,Z预先传递才有效,而不是顶点受限。 - ltjax

8

glColorMask和glDepthMask确定了哪些帧缓冲区域实际上会被写入。

早期Z剔除的思想是,首先只渲染深度缓冲区部分 - 实际节省的时间来自于将几何物体从近到远排序,这样GPU可以快速丢弃被遮挡的片段。但是在绘制Z缓冲区时,您不希望绘制颜色组件:这允许您关闭着色器、纹理等所有计算密集型操作。

警告一句话:早期Z仅适用于不透明的几何物体。实际上,整个深度缓冲区算法仅适用于不透明的物体。一旦您进行混合,您将不得不从远到近进行排序,并且不使用深度缓冲区(搜索“无序透明度”以获取克服相关问题的算法)。

因此,如果您有任何需要混合的内容,请将其从“早期Z”阶段中删除。

在第一次通行中,您设置:

glDepthMask(1); // enable depth buffer writes
glColorMask(0,0,0); // disable color buffer writes
glDepthFunc(GL_LESS); // use normal depth oder testing
glEnable(GL_DEPTH_TEST); // and we want to perform depth tests

完成Z通道后,您需要稍微调整一下设置。
glDepthMask(0); // don't write to the depth buffer
glColorMask(1,1,1); // now set the color component

glDepthFunc(GL_EQUAL); // only draw if the depth of the incoming fragment
                       // matches the depth already in the depth buffer

GL_LEQUAL也可以完成这项工作,但还允许比深度缓冲区中更近的片段通过。但由于不会更新深度缓冲区,因此每次在那里绘制东西时,原点和存储深度之间的任何东西都将覆盖它。

稍微改变一下主题,可以在多个延迟着色通道中使用“早期Z”填充的深度缓冲区作为几何缓冲区。

为了节省进一步的几何形状,请查看遮挡查询。使用遮挡查询可以询问GPU有多少个片段通过了所有测试。作为体素引擎,您可能正在使用八叉树或Kd树。在遍历分支之前绘制树的空间分隔面(使用glDepthMask(0),glColorMask(0,0,0))会告诉您该分支中是否可见任何几何形状。结合近到远排序遍历和对树进行(粗略的)视锥裁剪,这将给您带来巨大的性能优势。


非常好的评论!但是OP的代码似乎与您对early-z的描述一致。 - Kos
world_display() 可能会做一些不幸的事情,比如使用混合,可能会改变某些状态等。原则上,为了使“早期 Z”起作用,您需要三个单独的渲染路径:一个仅用于深度通道,一个用于不透明通道,最后一个用于所有半透明物体。 - datenwolf

5

z-pre pass可以与半透明物体一起使用。如果它们是半透明的,不要在预处理中渲染它们,然后进行深度排序并渲染。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接