11

我在 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;

上面实际上生成了像柏林噪声一样移动的光照!那么关于如何正确获得每个顶点法线的任何建议?

4

2 回答 2

11

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

于 2011-06-28T08:12:33.653 回答
7

您没有确切说明您实际上是如何产生职位的。因此,我将假设您正在使用 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 值相同的模型空间。这通常通过选择最大高度并适当缩放位置来完成。因此,我们修改后的 calc 位置看起来像这样:

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));

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

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

于 2011-06-28T08:29:20.750 回答