如何从 2D 梯度(通过梯度(perlin)噪声生成)获取 3D 法线向量

问题描述 投票:0回答:1

我在 GLSL 中实现了梯度噪声函数,以便生成地形的高度图。我试图为高度图的每个像素获取法线向量,以便我可以计算来自“太阳”的定向照明。该法线向量将与地形高度一起存储在纹理中,因此我们可以假设平坦表面的法线为

vec3(0, 0, -1);
(z 轴)。

梯度本身是一个2D向量。当我弄清楚这一点时,我目前将其简单化为一个八度,为此每个像素的噪声和梯度是这样生成的:

vec3 random_pcg3d(uvec3 v)
{
    v = v * 1664525u + 1013904223u;
    v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y;
    v ^= v >> 16u;
    v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y;
    return vec3(v) * (1.0/float(0xffffffffu));
}

vec2 random_gradient(uvec3 p)
{
    vec3 uv = random_pcg3d(p);
    float r = sqrt(uv[0]);
    float phi = 2.0 * M_PI * uv[1];
    return vec2(r * cos(phi), r * sin(phi));
}

vec3 gradient_noise(vec2 pos, float gridSize)
{
    vec2 gridPos = pos * gridSize;
    uvec2 i = uvec2(gridPos);
    vec2 f = fract(gridPos);

    vec2 g11 = random_gradient(uvec3(i.x, i.y, 1));
    vec2 g12 = random_gradient(uvec3(i.x + 1u, i.y, 1));
    vec2 g21 = random_gradient(uvec3(i.x, i.y + 1u, 1));
    vec2 g22 = random_gradient(uvec3(i.x + 1u, i.y + 1u, 1));

    float d11 = dot(g11, f);
    float d12 = dot(g12, f - vec2(1.0, 0.0));
    float d21 = dot(g21, f - vec2(0.0, 1.0));
    float d22 = dot(g22, f - vec2(1.0, 1.0));

    f = smoothstep(0.0, 1.0, f);

    vec2 gradient_lerp_x0 = mix(g11, g12, f.x);
    vec2 gradient_lerp_x1 = mix(g21, g22, f.x);
    vec2 gradient  = mix(gradient_lerp_x0, gradient_lerp_x1, f.y);

    float q1 = mix(d11, d12, f.x);
    float q2 = mix(d21, d22, f.x);
    float noise = mix(q1, q2, f.y);

    return vec3(noise, gradient.x, gradient.y);
}

这是来自 YouTube 教程的调整,不包括梯度输出,但我发现确认梯度也可以像噪声值一样进行插值的来源。

现在从梯度来看,我确信我应该拥有计算 3D 空间中的表面法线所需的所有信息。我已经成功地通过采样靠近像素的 2 个点并仅根据噪声数据估计梯度来做到这一点,这在一定精度下给出了相当好的结果,但我想准确地计算它,因为我已经有了梯度数据可用。

这意味着我有估计版本可以与直接计算的结果进行比较,并且我已经尝试了很多方法来计算法线,但到目前为止,我所做的一切都与估计相一致。我不能确定这个估计绝对是更正确的版本,但与高度图相比,它看起来更有希望,并且给出了迄今为止我所见过的最佳视觉结果,所以我假设它可能是我管理过的最好的。

这里有两次从 2D 梯度计算 3D 法线的尝试,其中第一个有一些注释掉的尝试来计算 3D 空间中的梯度方向,也许这是我出错的步骤:

vec3 calculate_surface_normal(float height, vec2 gradient)
{
    vec2 tangent_xy = normalize(gradient);
//    vec2 tangent_xy = vec2(1.0) / gradient;
    float tangent_z = length(gradient);
//    float tangent_z = 1;
//    float tangent_z = sqrt(1.0 - dot(tangent_xy, tangent_xy));

    vec3 gradient_forward = normalize(vec3(tangent_xy, tangent_z));
    vec3 gradient_right = vec3(rotate_by_phase(normalize(gradient), 0.25), 0);
    vec3 gradient_up = cross(gradient_right, gradient_forward);

    return gradient_up;
}

vec3 calculate_surface_normal(float height, vec2 gradient)
{
    // Compute the partial derivatives using finite differencing
    float dz_dx = gradient.x;
    float dz_dy = gradient.y;

    // Compute the surface normal using cross product
    vec3 tangent = normalize(vec3(1.0, 0.0, dz_dx));
    vec3 bitangent = normalize(vec3(0.0, 1.0, dz_dy));
    vec3 normal = normalize(cross(bitangent, tangent));

    // Negate z as up is simply -z
    normal.z *= -1.f;

    return normal;
}

我可以通过将它们一起显示来比较结果。这些图像都显示(从左到右,然后从上到下):

  • 噪声值作为高度图
  • 从梯度计算法线,同时产生噪声(法线方向矢量轴映射到RGB)
  • 通过将高度值与两个非常接近的区域进行比较来估计法线(法线方向矢量轴映射到 RGB)
  • 地形按高度着色并使用正常估计进行照明(如果高于水平面)

[从此处编辑]

第一次正常计算:

First normal calculation

第二次正常计算:

Second normal calculation

两个计算结果与估计结果有很大不同。

使用梯度->正常函数之一计算照明会产生没有意义的结果,因为我可以在地形高度/颜色细节中看到细节,而根据估计计算出的图像中的照明看起来是有意义的.

opengl glsl normals perlin-noise heightmap
1个回答
0
投票

想象一下,您有一个 3d 函数,即表面上方的高度。

f(x, y, z) = z - surface_height(x, y)

如果你取该函数的梯度,你将得到 ℝ³ 而不是 ℝ² 中的向量。

© www.soinside.com 2019 - 2024. All rights reserved.