opengl 柏林噪声的逐顶点法线?

yyhrrdl8  于 2023-06-22  发布在  其他
关注(0)|答案(3)|浏览(123)

我生成的地形在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;

上面实际上产生的灯光像柏林噪音一样四处移动!那么,对于如何正确地获得每个顶点的法线,有什么建议吗?

toiithl6

toiithl61#

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

628mspwn

628mspwn2#

你没有说你是如何产生这些头寸的。因此,我假设您正在使用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值将要缩放的模型空间相同的模型空间。这通常通过拾取最大高度并适当地缩放位置来完成。因此,我们修改后的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;
}

这两个矩阵变换到噪声函数的空间,然后变换回来。您的实际代码可以使用不太复杂的结构,这取决于您的用例,但完整的仿射变换很容易描述。
好了,现在我们已经建立了,你需要记住的是:没有什么是有意义的,除非你知道它在什么空间。你的正常,你的位置,什么都不重要,直到你确定它在什么空间。
此函数返回模型空间中的位置。我们需要计算 * 模型空间 * 中的法线。要做到这一点,我们需要三个位置:顶点的当前位置,以及从当前位置略微偏移的两个位置。我们得到的位置 * 必须 * 在模型空间中,否则我们的法线将不在。
因此,我们需要具备以下功能:

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的并行性为您工作。

gudnpqoy

gudnpqoy3#

一个链接,帮助我更好地可视化和理解这个逻辑。
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.);
}

结果如下:

相关问题