来自perlin噪音的每顶点法线?

Nit*_*x88 8 opengl shader noise normals

我正在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;
Run Code Online (Sandbox Code Playgroud)

以上实际产生的照明像perlin噪音一样四处移动!那么关于如何正确获得每顶点法线的任何建议?

dat*_*olf 9

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

  • @ Nitrex88:@Nicol Bolas的回答告诉你和我一样,但是更详细,更精细.他当然是正确的,你要跟踪你所处的空间.但是如果我们从功能的角度来看这个,那么归结为偏导数,即渐变.您"摆动"噪声采样坐标,结果噪声值相应地摆动.然后,只需将输入摆动空间矢量与输出摆动空间相关联即可. (3认同)

Nic*_*las 5

你没有确切地说出你实际上是如何产生这些职位的.所以我假设您正在使用Perlin噪声在高度图中生成高度值.因此,对于hieghtmap中的任何位置X,Y,您使用2D噪声函数来生成Z值.

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

vec3 CalcPosition(in vec2 loc) {
    float height = MyNoiseFunc2D(loc);
    return vec3(loc, height);
}
Run Code Online (Sandbox Code Playgroud)

这会生成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;
}
Run Code Online (Sandbox Code Playgroud)

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

好了,既然我们已经确定了这一点,那么你需要记住的是:没有任何意义,除非你知道它在哪个空间.你的正常,你的位置,没有关系,直到你建立它所在的空间.

此函数返回模型空间中的位置.我们需要计算模型空间中的法线.要做到这一点,我们需要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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

你应该了解你的噪音功能.

现在您在模型空间中有三个位置(当前位置,x偏移和y偏移),您可以在模型空间中计算顶点法线:

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

vec3 modelNormal = normalize(cross(modelXGrad, modelYGrad));
Run Code Online (Sandbox Code Playgroud)

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

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