7

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

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

8 面圆柱体与圆锥体

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

具有尖端平均的面部细节

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

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

4

2 回答 2

10

我在由三角形组成的现代 OpenGL(即着色器)中苦苦挣扎,但后来我发现了一个非常简单的解决方案!我会说它比当前接受的答案中建议的要好得多,也更简单。

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

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

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

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

在我的顶点着色器中,我只变换顶点位置(乘以投影和模型视图矩阵)并变换法线向量(乘以模型视图矩阵的变换逆矩阵)。然后将转换后的位置、法线向量和未转换的颜色传递给片段着色器,在其中我计算光方向和法线向量的点积,并将这个数字与颜色相乘。

让我从我所做的并发现不令人满意的事情开始:

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

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

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

尝试#3:除了我在锥尖顶点中分配的法向量等于零向量 vec3(0.0f, 0.0f, 0.0f) 之外,我按照尝试#2 进行了所有操作。这是诀窍的关键!然后这个零法线向量被传递给片段着色器,(即在顶点和片段着色器之间,它会自动插入其他两个顶点的法线向量)。当然,您需要对片段(!)着色器中的向量进行归一化,因为它没有恒定的大小 1(我需要点积)。所以我对其进行归一化——当然这对于法向量的大小为零的锥体尖端是不可能的。但它适用于所有其他点。就是这样。

有一件重要的事情要记住,要么你只能在片段着色器中对法线向量进行归一化。如果您尝试在 C++ 中对零大小的向量进行归一化,肯定会出错。因此,如果由于某种原因在进入片段着色器之前需要归一化,请确保排除大小为零的法线向量(即圆锥的尖端,否则会出错)。

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

这里是光滑圆锥的例子

于 2016-05-12T13:01:25.987 回答
4

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

在此处输入图像描述

这里有一些你可以尝试的东西......

  1. 使用四边形(如@WhitAngl 所说)。对新的 OpenGL而言,毕竟有四边形的用途

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

    在此处输入图像描述

  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;
        ...
    }
    

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

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

于 2013-12-17T17:02:12.510 回答