完全透明的OpenGL模型

5

请帮我解释一下我做错了什么。我已经将一个3D模型加载到自己编写的OpenGL渲染器(版本3.3)中,并尝试使用顶点着色器使其透明,就像X光效果一样:

#version 330

attribute vec3 coord3d;
attribute vec2 texcoord;
varying vec2 f_texcoord;

uniform mat4 projectionMatrix; 
uniform mat4 modelViewMatrix; 

layout (location = 0) in vec3 inPosition;
layout (location = 1) in vec4 inColor;

smooth out vec4 theColor;

void main()
{
    gl_Position = projectionMatrix*modelViewMatrix*vec4(inPosition, 1.0);
    theColor = vec4(0.0,0.2,0.4,0.4);
    f_texcoord = texcoord;

}

该模型在编辑器中进行了三角剖分,并使用以下方法绘制:

glDrawArrays(GL_TRIANGLE_FAN, 0, (vertices.at(i)->size()/3));

如果我使用

glEnable(GL_ALPHA_TEST);
glEnable( GL_BLEND );
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glEnable(GL_DEPTH_TEST);
glClearDepth(1.0); 

我看到一些不必要的三角形或线条:
如果没有深度测试,我会看到脸部内部有多个三角形(我不想要):
如何去除不需要的效果并实现类似Google Sketchup的X光效果?
如果我想使整个模型透明,是否应该实现深度排序?
如何实现这个:

3
我可以在Blender中使用相邻的立方体和0.4的Alpha值来重现类似的情况。简单的答案是:Z-fighting。移除其中一个重叠的面可以解决问题。 - thokra
1
另外,为什么要使用固定功能的 alpha 测试?为什么不使用片段着色器丢弃呢?一般来说,你需要兼容性还是只是太懒而不想手动做呢? ;) - thokra
+1 给 @thokra,你至少可以玩一下你的近距离和远距离值。 - johan d
如果我使用固定的颜色集,例如vec4(0.0, 0.2, 0.4, 0.4),我是否应该在着色器中执行alpha测试? - xcix
@xcix:不,仅仅因为你有一个恒定的alpha值并不意味着你应该使用alpha测试。如果你不想基于alpha值和相应的alpha函数丢弃片段,那么就不要使用alpha测试。Alpha测试在所谓的alpha剪裁中非常有用,其中你有一个矩形纹理的区域完全为空白,并且具有0的alpha值。 - thokra
显示剩余2条评论
2个回答

9
问题1: 首先,不要禁用深度测试。深度测试在混合之前执行,以确保片段正确丢弃,即使启用混合也是如此。有关每个片段操作以及它们执行的顺序的更多信息,请参见核心OpenGL 4.4规范的第17.3节。
当您禁用深度测试时,除非绘制调用的顺序绝对正确,否则您将获得意外的结果,从而违背深度缓冲区的目的。即使您正确排序了命令,您也不能期望所有副作用都消失,并且尝试按照您需要的方式对每个命令进行排序只适用于最简单的程序。
考虑以下非常简单的示例:
您可以看到3个四边形,最远的可视化通常的纹理坐标空间(s,t E [0,1]),一个与后者混合的蓝色四边形和一个不透明的红色四边形,应该在前面。显然,红色四边形不在前面,尽管深度值表明相反。同样明显的是,意图是将蓝色四边形与红色四边形混合。在蓝色四边形之后呈现红色四边形似乎可以解决问题,但是如果您绘制另一个四边形,该四边形应该在蓝色和红色四边形之后,它将仅以禁用深度测试的方式出现在顶部。这一切都错了。
黄色四边形遮挡了所有内容,尽管它应该在红色和蓝色四边形后面。正确的图像是通过纯粹启用深度测试获得的:
我的一般建议是:除非您有很好的理由禁用深度测试,例如在渲染叠加层时不想麻烦深度缓冲区,请保持深度测试启用。 注意:深度缓冲区不会解决您通常需要按照非不透明几何图形的顺序排列才能获得正确结果的问题!如果您想完全独立于排序并尝试模拟透明度,则应查找许多关于无序透明度的论文中的常见嫌疑人。 问题2: 从外观上看,我假设您正在使用以下混合函数:
gl::BlendFunc(gl::ONE,gl::ONE);
或导致稳定的连续值累积的任何内容,例如
gl::BlendFunc(gl::SRC_ALPHA,gl::ONE);
RGB和alpha分量的默认混合方程式:

gl::BlendEquation (gl::FUNC_ADD);

如果不是这种情况,请添加一条注释说明实际值,以便我重新检查并可能编辑我的建议。以下仍然适用,因为颜色永远不会说谎。 ;)
你的第二个问题是由于投影下的z-fighting。一些片段被正确地丢弃,而其他片段则没有被丢弃。由于你显然有重复的面在某些地方完全重合,因此可能会出现第一个面生成了稍高的深度值的情况。当渲染第二个面时,会生成稍低的深度值,并且该片段通过,导致在实际上不应该存在的地方出现了过度绘制。
第一个片段已经完成了所有操作,并将混合值写入帧缓冲区,将深度值写入深度缓冲区,即你有一个通过了所有测试并与背景混合的面的片段。现在来了第二个片段,它不会被丢弃,因为深度值稍低,并且再次与帧缓冲区中已经混合的颜色混合。由于加法混合,你得到了观察到的结果。我使用了与你使用的相同的颜色和alpha值在以下示例中重现了这种情况:

enter image description here

问题3:你的模型似乎有很多重复的面,在一般情况下,三角剖分看起来很糟糕。你应该重新做它。理解和使用混合已经够难了。不要用次优数据使它更加复杂。

我使用:code glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation (GL_FUNC_ADD) 尝试实现X光效果。如果您知道使所有对象半透明的正确方法(场景中没有不透明的对象),请告诉我。 - xcix
@xcix:是的,我也试过了。;) 我选择了 ONE, ONE 来更好地可视化累积效果。唯一的区别是:finalColor = SRC_ALPHA * source + destination 而不是 finalColor = source + destination。我已经相应地更新了我的答案。 - thokra

0

渲染透明物体的典型过程如下:

  1. 绘制不透明物体
  2. 将深度掩码设置为false:glDepthMask(GL_FALSE);
  3. 将透明三角形从后往前排序。
  4. 按此顺序渲染透明对象。
  5. 将深度掩码设置为true:glDepthMask(GL_TRUE);

这是您可以确保获得准确结果的唯一方法。不幸的是,深度排序既复杂又耗时。有时候,您可以跳过它并获得“足够好”的结果。还有一些无序透明技术,可以提供合理的结果。

在您的情况下,看起来避免深度排序是不可避免的。

编辑:在绘制透明对象时,您不能完全启用深度测试,因为这样绘制的任何东西后面的像素都会被覆盖。您也不能完全禁用它,否则您将能够看到即使透明对象位于不透明对象后面也是如此。解决方案是仍然检查您绘制的透明片段是否被更近(不透明)的片段遮挡,但不保存透明片段的深度值,因此它们不会被后续检查。用于告诉OpenGL停止/开始保存深度值的方法是glDepthMask()


你能详细说明为什么需要遮蔽深度写入吗? - thokra
我没有任何不透明的对象,所有的都应该是透明的,如果我禁用深度,那么我会得到一个像我发布的第二张截图一样的图像。 - xcix
如果您有相互交叉的三角形,您可能需要将它们分割,以便以正确的顺序呈现它们。深度排序是一个复杂的主题。如果您对此有更多问题,可以在网上查找或提出单独的问题。 - fintelia
请纠正我,但如果我分割三角形,这些三角形形成了面(四边形或更复杂的多边形),我会损坏我的模型。搜索三角形内部的中心点并计算该点与相机点(在gluLookAt中定义)之间的距离,并按此距离排序是一种正确的方法吗? - xcix
您可以通过它们的中心对三角形进行排序,尽管在某些情况下会产生不正确的结果。这解释了深度排序的一些困难之处:https://www.opengl.org/wiki/Transparency_Sorting - fintelia
显示剩余2条评论

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