由于GPU驱动供应商通常不会费心在GLSL中实现noiseX,我正在寻找一个“图形随机化瑞士军刀”实用函数集,最好是在GPU着色器中优化使用。我更喜欢GLSL,但是任何语言的代码对我来说都可以,我可以把它翻译成GLSL。
具体来说,我希望:
a)伪随机函数- n维,均匀分布于[-1,1]或[0,1]之上,从m维种子(理想情况下是任何值,但我同意将种子限制在,比如说,0..1为结果均匀分布)。喜欢的东西:
float random (T seed);
vec2 random2 (T seed);
vec3 random3 (T seed);
vec4 random4 (T seed);
// T being either float, vec2, vec3, vec4 - ideally.
b)连续的噪声,比如柏林噪声——同样是n维,+-均匀分布,有约束的值集,看起来不错(一些配置外观的选项,比如柏林电平也很有用)。我希望签名是这样的:
float noise (T coord, TT seed);
vec2 noise2 (T coord, TT seed);
// ...
我对随机数生成理论不太感兴趣,所以我最渴望的是一个现成的解决方案,但我也很喜欢这样的回答:“这里有一个非常好的,高效的1D rand(),让我解释一下如何在它的基础上制作一个好的n维rand()……”.
我突然想到,你可以使用一个简单的整数哈希函数,并将结果插入到浮点数的尾数中。IIRC, GLSL规范保证32位无符号整数和IEEE binary32浮点表示,因此它应该是完全可移植的。
我刚才试过了。结果非常好:我尝试的每个输入看起来都像静态的,根本没有可见的模式。相比之下,在相同的输入条件下,流行的sin/fract代码片段在我的GPU上有相当明显的对角线。
一个缺点是它需要GLSL v3.30。虽然它看起来足够快,但我还没有对它的表现进行实证量化。AMD的Shader Analyzer声称HD5870上的vec2版本每个时钟有13.33个像素。与sin/fract片段的每个时钟16像素相比。所以它肯定会慢一点。
这是我的实现。我把它写成了不同的形式这样更容易推导出你自己的函数。
/*
static.frag
by Spatial
05 July 2013
*/
#version 330 core
uniform float time;
out vec4 fragment;
// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint hash( uint x ) {
x += ( x << 10u );
x ^= ( x >> 6u );
x += ( x << 3u );
x ^= ( x >> 11u );
x += ( x << 15u );
return x;
}
// Compound versions of the hashing algorithm I whipped together.
uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y) ); }
uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ); }
uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); }
// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
float floatConstruct( uint m ) {
const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
const uint ieeeOne = 0x3F800000u; // 1.0 in IEEE binary32
m &= ieeeMantissa; // Keep only mantissa bits (fractional part)
m |= ieeeOne; // Add fractional part to 1.0
float f = uintBitsToFloat( m ); // Range [1:2]
return f - 1.0; // Range [0:1]
}
// Pseudo-random value in half-open range [0:1].
float random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); }
float random( vec2 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec3 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec4 v ) { return floatConstruct(hash(floatBitsToUint(v))); }
void main()
{
vec3 inputs = vec3( gl_FragCoord.xy, time ); // Spatial and temporal inputs
float rand = random( inputs ); // Random per-pixel value
vec3 luma = vec3( rand ); // Expand to RGB
fragment = vec4( luma, 1.0 );
}
截图:
我在一个图像编辑程序中检查了截图。有256种颜色,平均值是127,这意味着分布均匀,涵盖了预期的范围。
我也有同样的问题,我需要在WebGL 1.0中实现它,所以我不能使用之前回答中给出的一些例子。我尝试了前面提到的Gold Noise,但是PHI的使用并不适合我。(距离(xy * PHI, xy) *种子就等于长度(xy) * (1.0 - PHI) *种子,所以我不明白当它直接乘以种子时,PHI的魔力应该如何发挥作用?
不管怎样,我做了类似的事情,只是没有PHI,而是在另一个地方添加了一些变化,基本上我取xy与框架外的某个随机点之间的距离的tan,然后乘以xy与另一个这样的随机点之间的距离,位于左下角(所以这些点之间没有偶然的匹配)。在我看来还不错。单击生成新的帧。
(function main() {
const dim = [512, 512];
twgl.setDefaults({ attribPrefix: "a_" });
const gl = twgl.getContext(document.querySelector("canvas"));
gl.canvas.width = dim[0];
gl.canvas.height = dim[1];
const bfi = twgl.primitives.createXYQuadBufferInfo(gl);
const pgi = twgl.createProgramInfo(gl, ["vs", "fs"]);
gl.canvas.onclick = (() => {
twgl.bindFramebufferInfo(gl, null);
gl.useProgram(pgi.program);
twgl.setUniforms(pgi, {
u_resolution: dim,
u_seed: Array(4).fill().map(Math.random)
});
twgl.setBuffersAndAttributes(gl, pgi, bfi);
twgl.drawBufferInfo(gl, bfi);
});
})();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;
void main() {
gl_Position = a_position;
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_seed[2];
void main() {
float uni = fract(
tan(distance(
gl_FragCoord.xy,
u_resolution * (u_seed[0] + 1.0)
)) * distance(
gl_FragCoord.xy,
u_resolution * (u_seed[1] - 2.0)
)
);
gl_FragColor = vec4(uni, uni, uni, 1.0);
}
</script>
<canvas></canvas>
一定要用这个:
highp float rand(vec2 co)
{
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}
不要用这个:
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
您可以在改进OpenGL ES 2.0的规范一行程序GLSL rand()中找到解释
在2010年首次发布这个问题之后,在良好的随机函数和硬件支持方面发生了很大的变化。
从今天的角度来看,这个算法抽取的随机数的一致性很差。均匀性受到输入值大小的很大影响,当从输入值中采样时,例如射线/路径跟踪应用程序,可见的工件/模式将变得明显。
There have been many different functions (most of them integer hashing) being devised for this task, for different input and output dimensionality, most of which are being evaluated in the 2020 JCGT paper Hash Functions for GPU Rendering. Depending on your needs you could select a function from the list of proposed functions in that paper and simply from the accompanying Shadertoy.
One that isn't covered in this paper but that has served me very well without any noticeably patterns on any input magnitude values is also one that I want to highlight.
其他类型的算法使用低差异序列来绘制伪随机数,例如带有Owen-Nayar置乱的Sobol序列。Eric Heitz在这一领域做了一些惊人的研究,以及他在Screen Space论文中将蒙特卡罗误差分布为蓝色噪声的低差异采样器。
另一个例子是(迄今为止最新的)JCGT论文基于实用哈希的Owen置乱,它将Owen置乱应用于不同的哈希函数(即Laine-Karras)。
然而,其他类别使用算法来产生具有理想频谱的噪声模式,例如蓝色噪声,它特别“取悦”眼睛。
(我意识到好的StackOverflow答案应该提供算法作为源代码,而不是链接,因为那些可能会中断,但现在有太多不同的算法,我打算把这个答案作为今天已知的好算法的总结)