低多边形锥 - 尖端的平滑阴影

时间:2013-03-07 23:00:45

标签: opengl 3d geometry directx mesh

如果将圆柱体细分为8面棱镜,根据位置计算顶点法线(“平滑着色”),它看起来相当不错。

如果你将一个圆锥体细分为一个8面金字塔,根据它们的位置计算法线,你就会卡在锥体的顶端(技术上是锥体的顶点,但我们称之为尖端,以避免与锥体混淆)网格顶点)。

8-sided cylinder vs cone

对于每个三角形面,您希望沿两条边匹配法线。但是因为你只能在三角形的每个顶点指定一个法线,你可以匹配一个边缘或另一个边缘,但不能同时匹配两个边缘。您可以通过选择两条边的平均值的尖端法线来妥协,但现在没有一条边看起来很好。以下是选择每个尖端顶点的平均法线的详细信息。

face detail with tip averaging

在一个完美的世界中,GPU可以光栅化真正的四边形,而不仅仅是三角形。然后我们可以用退化四边形指定每个面,允许我们为每个三角形的两个相邻边指定不同的法线。但我们必须使用的是三角形...我们可以将锥体切割成多个“堆叠”,这样边缘不连续性只能在锥体的尖端而不是整个物体上看到,但是仍然会有小费!

任何人都有光滑阴影的低多边形锥体的技巧吗?

2 个答案:

答案 0 :(得分:6)

我正在用三角形构成的现代OpenGL(即着色器)中的锥体挣扎,但后来我发现了一个非常简单的解决方案!我会说它比目前接受的答案中建议的更好更简单。

我有一个三角形阵列(显然每个都有3个顶点),形成锥面。我不关心底面(圆形底座),因为这非常简单。在我的所有工作中,我使用以下简单的顶点结构:

  • position: vec3(通过添加1.0f作为最后一个元素,在着色器中自动转换为vec4)

  • normal_vector: vec3(在着色器中保留为vec3,因为它用于计算带有光方向的点积)

  • color: vec3(我没有使用透明度)

在我的顶点着色器中,我只是转换顶点位置(乘以投影和模型视图矩阵)并且还转换法向量(乘以模型 - 视图矩阵的变换逆)。然后将变换后的位置,法线向量和未变换的颜色传递给片段着色器,在那里我计算了光方向和法向量的点积,并将该数乘以颜色。

让我从我的所作所为开始,并发现不满意:

尝试#1 :每个锥面(三角形)使用恒定法向量,即一个三角形的所有顶点具有相同的法向量。 这很简单,但没有实现平滑的光照,每个面都有一个恒定的颜色,因为三角形的所有碎片都具有相同的法向量。错。

尝试#2 :我分别计算了每个顶点的法向量。对锥体圆形底座上的顶点来说这很容易,但锥体的顶端应该使用什么?我使用整个三角形的法向量(即与尝试#中的值相同)。这是更好的,因为我在靠近锥体底部的部分有光滑的照明,但在尖端附近不光滑。错。

但后来我找到了解决方案:

尝试#3 :我在尝试#2中做了所有事情,除了我在锥顶点顶点指定了法线向量等于零向量vec3(0.0f,0.0f,0.0 F)。这是技巧的关键!然后将这个零法线向量传递给片段着色器(即,在顶点和片段着色器之间,它会自动用其他两个顶点的法线向量进行插值)。当然那么你需要规范化片段(!)着色器中的向量,因为它的常量大小不是1(我需要点积)。所以我将它标准化 - 当然这对于法线向量大小为零的锥体的尖端是不可能的。但它适用于所有其他方面。就是这样。

有一件重要的事情需要记住,要么你只能在片段着色器中规范化法线向量。如果你试图在C ++中规范化零大小的向量,你肯定会得到错误。因此,如果由于某种原因在进入片段着色器之前需要进行标准化,请确保排除大小为零的法向量(即锥体的尖端或者您将得到错误)。

除锥形尖端以外的所有点都会产生锥体的平滑阴影。但这一点并不重要(谁关心一个像素......)或者你可以用特殊的方式处理它。另一个优点是你甚至可以使用非常简单的着色器。唯一的变化是在片段着色器中而不是在顶点着色器中甚至在之前标准化法向量。

example of smooth cone here

答案 1 :(得分:2)

是的,它肯定是三角形的限制。我认为当你从圆柱体接近圆锥体时显示问题会使问题变得非常清楚:

enter image description here

这是你可以尝试的一些事情......

  1. 使用四边形(如@WhitAngl所说)。对于新的OpenGL来说,毕竟是用于四边形。

  2. 更均匀地镶嵌细分。将尖端的法线设置为公共向上矢量可以消除任何粗糙的边缘,尽管在未点亮的一侧看起来有点奇怪。不幸的是,这违背了你的问题标题,低多边形锥

    enter image description here

  3. 确保您的圆锥体围绕对象空间原点居中(或在顶点着色器中以程序方式生成圆锥体),使用片段位置生成法线...

    in vec2 coneSlope; //normal x/z magnitude and y
    in vec3 objectSpaceFragPos;
    
    uniform mat3 normalMatrix;
    
    void main()
    {
        vec3 osNormal = vec3(normalize(objectSpaceFragPos.xz) * coneSlope.x, coneSlope.y);
        vec3 esNormal = normalMatrix * osNormal;
        ...
    }
    

    也许你可以采取一些花哨的技巧来减少片段着色器操作。 然后是更多与更昂贵的着色器进行细分的整体平衡。

  4. 圆锥是一个相当简单的对象,虽然我喜欢挑战,但在实践中我看不出这是一个问题,除非你想要很多的圆锥体。在这种情况下,您可能会进入几何着色器或实例化。更好的是,您可以使用片段着色器中的四边形和光线投射隐式锥体来绘制锥体。如果锥体都在平面上,您可以尝试法线贴图甚至视差贴图。