您没有确切说明您实际上是如何产生职位的。因此,我将假设您正在使用 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 的并行性为您工作。