如何从片段着色器中的 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 晶格的每个关节处有一个三分量渐变。并且参数完全是 x
、y
以及时间变量,但顺序严格。比如说,一个点 (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
中正确地随机化这些 s1
、s2
和 s3
?我是所有这些随机数生成方面的真正初学者,当然不是数学专家。
我拥有的 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);
我需要创建一个类似烟雾的动画纹理。我可以使用从 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 晶格的每个关节处有一个三分量渐变。并且参数完全是 x
、y
以及时间变量,但顺序严格。比如说,一个点 (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
中正确地随机化这些 s1
、s2
和 s3
?我是所有这些随机数生成方面的真正初学者,当然不是数学专家。
我拥有的 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);