如何从片段着色器中的 3 个传递值创建 3D 随机渐变?

How to create a 3D random gradient out of 3 passed values in a fragment shader?

我需要创建一个类似烟雾的动画纹理。我可以使用从 CPU 侧传递的渐变通过 3D 柏林噪声实现这一点,我做到了:

但在当前项目中,我无法从普通 cpp 代码传递数组。我仅限于编写 HLSL 着色器(尽管以下所有内容都是用 GLSL 编写的,因为它更容易设置)。所以我想我需要在片段着色器中为我的渐变生成某种随机值。在研究如何解决这个问题时,我发现我实际上可以使用散列函数作为我的伪随机值。我正在关注这些文章 (the first and the second),因此我选择使用 PCG 哈希来达到我的目的。我设法用以下代码生成了看起来不错的值噪声。

#version 420 core

#define MAX_TABLE_SIZE 256
#define MASK (MAX_TABLE_SIZE - 1)

in vec2 TexCoord;

out vec4 FragColor;

uniform float Time;

uint pcg_hash(uint input)
{
    uint state = input * 747796405u + 2891336453u;
    uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
    return (word >> 22u) ^ word;
}

// This is taken from here
// 
float ConvertToFloat(uint n)
{
    uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
    uint ieeeOne = 0x3F800000u;      // 1.0 in IEEE binary32

    n &= ieeeMantissa;
    n |= ieeeOne;

    float f = uintBitsToFloat(n);
    return f - 1.0;
}

float Random1(uint x, uint y)
{
    uint hash = pcg_hash(y ^ pcg_hash(x));
    return ConvertToFloat(hash);
}

float ValueNoise(vec2 p)
{
    int xi = int(p.x);
    uint rx0 = uint(xi & MASK);
    uint rx1 = uint((xi + 1) & MASK);

    int yi = int(p.y);
    uint ry0 = uint(yi & MASK);
    uint ry1 = uint((yi + 1) & MASK);

    float tx = p.x - float(xi);
    float ty = p.y - float(yi);

    float r00 = Random1(rx0, ry0);
    float r10 = Random1(rx1, ry0);
    float r01 = Random1(rx0, ry1);
    float r11 = Random1(rx1, ry1);

    float sx = smoothstep(0, 1, tx);
    float sy = smoothstep(0, 1, ty);

    float lerp0 = mix(r00, r10, sx);
    float lerp1 = mix(r01, r11, sx);

    return mix(lerp0, lerp1, sy);
}

float FractalNoise(vec2 point)
{
    float sum = 0.0;
    float frequency = 0.01;
    float amplitude = 1;
    int nLayers = 5;

    for (int i = 0; i < nLayers; i++)
    {
        float noise = ValueNoise(point * frequency) * amplitude * 0.5;
        sum += noise;
        amplitude *= 0.5;
        frequency *= 2.0;
    }

    return sum;
}

void main()
{
    // Coordinates go from 0.0 to 1.0 both horizontally and vertically
    vec2 Point = TexCoord * 2000;
    float noise = FractalNoise(Point);
    FragColor = vec4(noise, noise, noise, 1.0);
}

然而,我想要的是根据我传递的三个参数生成一个 3D 随机梯度(实际上只是一个 3D 随机向量),然后将其输入 Perlin 噪声函数。但我不知道如何正确地做到这一点。澄清一下这三个参数:看,我需要一个动画 Perlin 噪声,这意味着我需要在 3D 晶格的每个关节处有一个三分量渐变。并且参数完全是 xy 以及时间变量,但顺序严格。比如说,一个点 (1, 4, 5) 产生一个梯度 (0.1, 0.03, 0.78),但是一个点 (4, 1, 5) 应该产生一个完全不同的梯度,比如说 (0.22, 0.95, 0.43)。同样,顺序很重要。

我想出的(以及我从相关文章中可以理解的)是我可以按顺序对参数进行哈希处理,然后将结果值用作同一个哈希函数的种子,该函数现在将作为一个随机数发生器。所以我写了这个函数:

vec3 RandomGradient3(int x, int y, int z)
{
    uint seed = pcg_hash(z ^ pcg_hash(y ^ pcg_hash(x)));
    uint s1 = seed ^ pcg_hash(seed);
    uint s2 = s1 ^ pcg_hash(s1);
    uint s3 = s2 ^ pcg_hash(s2);

    float g1 = ConvertToFloat(s1);
    float g2 = ConvertToFloat(s2);
    float g3 = ConvertToFloat(s3);

    return vec3(g1, g2, g3);
}

然后我将梯度提供给 3D 柏林噪声函数:

float CalculatePerlin3D(vec2 p)
{
    float z = Time; // a uniform variable passed from the CPU side
    int xi0 = int(floor(p.x)) & MASK;
    int yi0 = int(floor(p.y)) & MASK;
    int zi0 = int(floor(z))   & MASK;

    int xi1 = (xi0 + 1) & MASK;
    int yi1 = (yi0 + 1) & MASK;
    int zi1 = (zi0 + 1) & MASK;

    float tx = p.x - int(floor(p.x));
    float ty = p.y - int(floor(p.y));
    float tz = z   - int(floor(z));

    float u = smoothstep(0, 1, tx);
    float v = smoothstep(0, 1, ty);
    float w = smoothstep(0, 1, tz);

    vec3 c000 = RandomGradient3(xi0, yi0, zi0);
    vec3 c100 = RandomGradient3(xi1, yi0, zi0);
    vec3 c010 = RandomGradient3(xi0, yi1, zi0);
    vec3 c110 = RandomGradient3(xi1, yi1, zi0);
    vec3 c001 = RandomGradient3(xi0, yi0, zi1);
    vec3 c101 = RandomGradient3(xi1, yi0, zi1);
    vec3 c011 = RandomGradient3(xi0, yi1, zi1);
    vec3 c111 = RandomGradient3(xi1, yi1, zi1);

    float x0 = tx, x1 = tx - 1;
    float y0 = ty, y1 = ty - 1;
    float z0 = tz, z1 = tz - 1;

    vec3 p000 = vec3(x0, y0, z0);
    vec3 p100 = vec3(x1, y0, z0);
    vec3 p010 = vec3(x0, y1, z0);
    vec3 p110 = vec3(x1, y1, z0);

    vec3 p001 = vec3(x0, y0, z1);
    vec3 p101 = vec3(x1, y0, z1);
    vec3 p011 = vec3(x0, y1, z1);
    vec3 p111 = vec3(x1, y1, z1);

    float a = mix(dot(c000, p000), dot(c100, p100), u);
    float b = mix(dot(c010, p010), dot(c110, p110), u);
    float c = mix(dot(c001, p001), dot(c101, p101), u);
    float d = mix(dot(c011, p011), dot(c111, p111), u);

    float e = mix(a, b, v);
    float f = mix(c, d, v);

    float noise = mix(e, f, w);
    float unsignedNoise = (noise + 1.0) / 2.0;

    return unsignedNoise;
}

使用这个RandomGradient3函数,产生了以下噪声纹理:

所以梯度似乎是相关的,因此噪音并不是真正随机的。问题是,我怎样才能从 RandomGradient3 中正确地随机化这些 s1s2s3?我是所有这些随机数生成方面的真正初学者,当然不是数学专家。

我拥有的 3D 柏林噪声函数似乎很好,因为如果我用 CPU 的预定义梯度输入它,它会产生预期的结果。

哦,好吧。发布问题后,我意识到我没有正确缩放生成的渐变!该函数产生的梯度范围为 [0.0; 1.0] 但我们实际上需要 [-1.0; 1.0] 使其工作。所以我重写了这段代码

vec3 RandomGradient3(int x, int y, int z)
{
    uint seed = pcg_hash(z ^ pcg_hash(y ^ pcg_hash(x)));
    uint s1 = seed ^ pcg_hash(seed);
    uint s2 = s1 ^ pcg_hash(s1);
    uint s3 = s2 ^ pcg_hash(s2);

    float g1 = ConvertToFloat(s1);
    float g2 = ConvertToFloat(s2);
    float g3 = ConvertToFloat(s3);

    return vec3(g1, g2, g3);
}

为此:

vec3 RandomGradient3(int x, int y, int z)
{
    uint seed = pcg_hash(z ^ pcg_hash(y ^ pcg_hash(x)));
    uint s1 = seed ^ pcg_hash(seed);
    uint s2 = s1 ^ pcg_hash(s1);
    uint s3 = s2 ^ pcg_hash(s2);

    float g1 = (ConvertToFloat(s1) - 0.5) * 2.0;
    float g2 = (ConvertToFloat(s2) - 0.5) * 2.0;
    float g3 = (ConvertToFloat(s3) - 0.5) * 2.0;

    return vec3(g1, g2, g3);
}

动画现在看起来符合预期:

不过,我还有一个问题。这些计算是否会产生非常好的伪随机数,我们可以依赖这些伪随机数来生成随机纹理?或者有更好的方法吗?显然,它们产生了足够好的结果,我们可以从上面的 GIF 中假设,但仍然如此。当然,我可以深入研究统计数据和其他东西,但也许有人能快速回答。

uint seed = pcg_hash(z ^ pcg_hash(y ^ pcg_hash(x)));
uint s1 = seed ^ pcg_hash(seed);
uint s2 = s1 ^ pcg_hash(s1);
uint s3 = s2 ^ pcg_hash(s2);