来自柏林噪声的每顶点法线?

发布于 2024-11-17 23:26:11 字数 656 浏览 7 评论 0原文

我正在 Opengl 几何着色器中生成地形,但在计算照明法线时遇到问题。我使用几何着色器中实现的柏林噪声函数动态生成每帧地形。因此,我需要一种基于噪声函数(没有纹理或任何东西)的有效方法来计算每个顶点的法线。我可以采用 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;

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

I'm generating terrain in Opengl geometry shader and am having trouble calculating normals for lighting. I'm generating the terrain dynamically each frame with a perlin noise function implemented in the geometry shader. Because of this, I need an efficient way to calculate normals per-vertex based on the noise function (no texture or anything). I am able to take cross product of 2 side to get face normals, but they are generated dynamically with the geometry so I cannot then go back and smooth the face normals for vertex normals. How can I get vertex normals on the fly just using the noise function that generates the height of my terrain in the y plane (therefore height being between 1 and -1). I believe I have to sample the noise function 4 times for each vertex, but I tried something like the following and it didn't work...

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;

The above actually generated lighting that moved around like perlin noise! So any advice for how I can get the per-vertex normals correctly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

终陌 2024-11-24 23:26:11

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

The normal is the vector perpendicular to the tangent (also known as slope). The slope of a function is its derivative; for n dimensions its n partial derivatives. So you sample the noise around a center point P and at P ± (δx, 0) and P ± (0, δy), with δx, δy choosen to be as small as possible, but large enough for numerical stability. This yields you the tangents in each direction. Then you take the cross product of them, normalize the result and got the normal at P.

送君千里 2024-11-24 23:26:11

您没有具体说明您实际上是如何生成这些职位的。因此,我假设您使用 Perlin 噪声来生成高度图中的高度值。因此,对于高度图中的任何位置 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));

从这里开始,执行常规操作。但是永远不要忘记跟踪各种向量的空间。

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

You didn't say exactly how you were actually generating the positions. So I'm going to assume that you're using the Perlin noise to generate height values in a height map. So, for any position X, Y in the hieghtmap, you use a 2D noise function to generate the Z value.

So, let's assume that your position is computed as follows:

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

This generates a 3D position. But in what space is this position in? That's the question.

Most noise functions expect loc to be two values on some particular floating-point range. How good your noise function is will determine what range you can pass values in. Now, if your model space 2D positions are not guaranteed to be within the noise function's range, then you need to transform them to that range, do the computations, and then transform it back to model space.

In so doing, you now have a 3D position. The transform for the X and Y values is simple (the reverse of the transform to the noise function's space), but what of the Z? Here, you have to apply some kind of scale to the height. The noise function will return a number on the range [0, 1), so you need to scale this range to the same model space that your X and Y values are going to. This is typically done by picking a maximum height and scaling the position appropriately. Therefore, our revised calc position looks something like this:

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

The two matrices transform to the noise function's space, and then transform back. Your actual code could use less complicated structures, depending on your use case, but a full affine transformation is simple to describe.

OK, now that we have established that, what you need to keep in mind is this: nothing makes sense unless you know what space it is in. Your normal, your positions, nothing matters until you establish what space it is in.

This function returns positions in model space. We need to calculate normals in model space. To do that, we need 3 positions: the current position of the vertex, and two positions that are slightly offset from the current position. The positions we get must be in model space, or our normal will not be.

Therefore, we need to have the following function:

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

Obviously, you can merge these two functions into one.

The delta value is a small offset in the space of the noise texture's input. The size of this offset depends on your noise function; it needs to be big enough to return a height that is significantly different from the one returned by the actual current position. But it needs to be small enough that you aren't pulling from random parts of the noise distribution.

You should get to know your noise function.

Now that you have the three positions (the current position, the x-offset, and the y-offset) in model space, you can compute the vertex normal in model space:

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

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

From here, do the usual things. But never forget to keep track of the spaces of your various vectors.

Oh, and one more thing: this should be done in the vertex shader. There's no reason to do this in a geometry shader, since none of the computations affect other vertices. Let the GPU's parallelism work for you.

尘曦 2024-11-24 23:26:11

这个链接帮助我更好地想象和理解这个逻辑。

https://www.scratchapixel.com/lessons/procedural- Generation-virtual-worlds/perlin-noise-part-2/perlin-noise-computing-derivatives.html

这是我的实现:

void main() {
    vec2 position = gl_FragCoord.xy;
    float point_value = perlin(position);

    // best offset for my aplication, tweek if necessary
    float delta = 0.032;

    // point a bit to the right of the original value
    vec2 position_offset_x = position + vec2(delta,0);
    // what is its perlin value
    float point_value_x = perlin(position_offset_x);
    // a vector from the point to the other one, using the perlin result
    // as the third dimension
    vec3 tangent_x = normalize(vec3(position,point_value) - vec3(position_offset_x,point_value_x));


    // same for Y
...

    // cross product of the two tangents of the point will create
    // the normal vector at that point
    vec3 norm = normalize(cross(tangent_x,tangent_y));

    // in this case, render the texture depicting the normals, in my 
    // case i used this norm to calculate how light affects this texture.
    gl_FragColor = vec4(norm,1.);
}

结果:

具有柏林噪声法线的纹理

A link that helped me visualize and understand better this logic.

https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/perlin-noise-part-2/perlin-noise-computing-derivatives.html

This was my implementation:

void main() {
    vec2 position = gl_FragCoord.xy;
    float point_value = perlin(position);

    // best offset for my aplication, tweek if necessary
    float delta = 0.032;

    // point a bit to the right of the original value
    vec2 position_offset_x = position + vec2(delta,0);
    // what is its perlin value
    float point_value_x = perlin(position_offset_x);
    // a vector from the point to the other one, using the perlin result
    // as the third dimension
    vec3 tangent_x = normalize(vec3(position,point_value) - vec3(position_offset_x,point_value_x));


    // same for Y
...

    // cross product of the two tangents of the point will create
    // the normal vector at that point
    vec3 norm = normalize(cross(tangent_x,tangent_y));

    // in this case, render the texture depicting the normals, in my 
    // case i used this norm to calculate how light affects this texture.
    gl_FragColor = vec4(norm,1.);
}

Results:

Texture with normals of perlin noise

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文