柏林噪声的每顶点法线?

Posted

技术标签:

【中文标题】柏林噪声的每顶点法线?【英文标题】:Per-Vertex Normals from perlin noise? 【发布时间】:2011-09-24 02:23:39 【问题描述】:

我在 Opengl 几何着色器中生成地形,但在计算光照法线时遇到问题。我使用几何着色器中实现的 perlin 噪声函数在每一帧动态生成地形。因此,我需要一种基于噪声函数(无纹理或任何东西)计算每个顶点法线的有效方法。我可以取 2 边的叉积来获得面法线,但它们是随几何图形动态生成的,因此我无法返回并平滑顶点法线的面法线。如何仅使用在 y 平面上生成地形高度的噪声函数(因此高度介于 1 和 -1 之间)来动态获取顶点法线。我相信我必须为每个顶点对噪声函数进行 4 次采样,但我尝试了类似以下的方法,但它没有用......

vec3 xP1 = vertex + vec3(1.0, 0.0, 0.0);
vec3 xN1 = vertex + vec3(-1.0, 0.0, 0.0);
vec3 zP1 = vertex + vec3(0.0, 0.0, 1.0);
vec3 zN1 = vertex + vec3(0.0, 0.0, -1.0);

float sx = snoise(xP1) - snoise(xN1);
float sz = snoise(zP1) - snoise(zN1);

vec3 n = vec3(-sx, 1.0, sz);
normalize(n);

return n;

上面实际上产生了像柏林噪声一样移动的光照!那么对于如何正确获取每个顶点法线有什么建议吗?

【问题讨论】:

【参考方案1】:

法线是垂直于切线的向量(也称为斜率)。函数的斜率是它的导数;对于 n 维,它的 n 个偏导数。因此,您在中心点 P 周围和 P ± (δx, 0) 和 P ± (0, δy) 处对噪声进行采样,其中 δx、δy 选择尽可能小,但要足够大以保证数值稳定性。这会产生每个方向的切线。然后你取它们的叉积,归一化结果并在 P 处得到法线。

【讨论】:

+1:这是正确的方法,虽然我不知道在着色器中这样做有多实用。 这个答案和另一个更长的答案(基本上)说的一样,对吧?无论如何,我明天会试试看它是否有效。 @Nitrex88:@Nicol Bolas 的回答和我一样告诉你,但形式更详细、更详细。他当然是对的,你必须跟踪你所在的空间。但是,如果我们从函数的角度来看,那么它归结为偏导数,即梯度。您“摆动”噪声采样坐标,生成的噪声值也会相应地摆动。然后只需将输入摆动空间向量与输出摆动空间相关联即可。【参考方案2】:

您没有确切说明您是如何实际生成职位的。因此,我将假设您正在使用 Perlin 噪声在高度图中生成高度值。因此,对于 hieghtmap 中的任何位置 X、Y,您使用 2D 噪声函数来生成 Z 值。

所以,假设您的位置计算如下:

vec3 CalcPosition(in vec2 loc) 
    float height = MyNoiseFunc2D(loc);
    return vec3(loc, height);

这会生成一个 3D 位置。但是这个位置在什么空间中?就是这个问题。

大多数噪声函数期望loc 是某个特定浮点范围内的两个值。您的噪声函数有多好将决定您可以传递值的范围。现在,如果您的模型空间 2D 位置不能保证在噪声函数的范围内,那么您需要将它们转换到该范围,进行计算,然后然后将其返回到模型空间。

这样做,您现在有了一个 3D 位置。 X 和 Y 值的变换很简单(与噪声函数空间的变换相反),但是 Z 呢?在这里,您必须对高度应用某种比例。噪声函数将返回 [0, 1) 范围内的数字,因此您需要将此范围缩放到与 X 和 Y 值相同的模型空间。这通常通过选择最大高度并适当缩放位置来完成。因此,我们修改后的计算位置如下所示:

vec3 CalcPosition(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel)

    vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
    float height = MyNoiseFunc2D(loc);
    vec4 modelPos = noiseToModel * vec4(loc, height, 1.0);
    return modelPos.xyz;

这两个矩阵变换到噪声函数的空间,然后再变换回来。您的实际代码可以使用不太复杂的结构,具体取决于您的用例,但完整的仿射变换很容易描述。

好的,既然我们已经确定了这一点,那么您需要记住的是:除非您知道它所在的空间,否则没有任何意义。在您确定它所在的空间之前,您的正常、您的位置都无关紧要.

此函数返回模型空间中的位置。我们需要在模型空间中计算法线。为此,我们需要 3 个位置:顶点的当前位置,以及与当前位置稍有偏移的两个位置。我们得到的位置必须在模型空间中,否则我们的法线将不在。

因此,我们需要具备以下功能:

void CalcDeltas(in vec2 modelLoc, const in mat3 modelToNoise, const in mat4 noiseToModel, out vec3 modelXOffset, out vec3 modelYOffset)

    vec2 loc = modelToNoise * vec3(modelLoc, 1.0);
    vec2 xOffsetLoc = loc + vec2(delta, 0.0);
    vec2 yOffsetLoc = loc + vec2(0.0, delta);
    float xOffsetHeight = MyNoiseFunc2D(xOffsetLoc);
    float yOffsetHeight = MyNoiseFunc2D(yOffsetLoc);
    modelXOffset = (noiseToModel * vec4(xOffsetLoc, xOffsetHeight, 1.0)).xyz;
    modelYOffset = (noiseToModel * vec4(yOffsetLoc, yOffsetHeight, 1.0)).xyz;

显然,您可以将这两个函数合并为一个。

delta 值是噪声纹理输入空间中的一个小偏移量。这个偏移量的大小取决于你的噪声函数;它需要足够大以返回与实际当前位置返回的高度有很大不同的高度。但它必须足够小,以免您从噪声分布的随机部分中拉出来。

你应该了解你的噪声函数。

现在您已经在模型空间中获得了三个位置(当前位置、x 偏移量和 y 偏移量),您可以在模型空间中计算顶点法线:

vec3 modelXGrad = modelXOffset - modelPosition;
vec3 modelYGrad = modelYOffset - modelPosition;

vec3 modelNormal = normalize(cross(modelXGrad, modelYGrad));

从这里开始,做平常的事情。但是永远不要忘记跟踪各种向量的空间。

哦,还有一件事:这应该在 vertex 着色器中完成。没有理由在几何着色器中这样做,因为所有计算都不会影响其他顶点。让 GPU 的并行性为您服务。

【讨论】:

我想过这样的方法,尝试过,但失败了。阅读您的答案后,我可能没有将所有内容都放在正确的空间中,这会导致错误。我正在使用顶点、几何和片段着色器,所以我意识到我必须格外小心将东西放在什么空间中。明天我将再次尝试使用这种方法以明确的方法,看看会发生什么。跨度> 我用这种方法得到了它。但是,为了让它工作,我需要制作 4 个偏移向量并使用偏移之间的梯度(而不是使用 modelPosition 的偏移梯度)。为了使照明方向正确,我必须切换获取 modelXGrad 和 modelYGrad 的叉积的顺序(仅提及遇到此解决方案的其他人)。感谢您用坐标空间很好地解释它......这有帮助。另外,我想提一下我在几何着色器中这样做是因为我在几何着色器中动态地细分地形

以上是关于柏林噪声的每顶点法线?的主要内容,如果未能解决你的问题,请参考以下文章

柏林噪声原理介绍

需要分数布朗噪声(FBM)与柏林噪声澄清

(转载)柏林噪声

2D 柏林噪声

运动的柏林噪音?

unity3d 柏林噪声 PerlinNoise 规律 算法