一、白噪声
白噪声常与伪随机数一起使用。这样,固定的输入就会产出固定的随机数输出,最终渲染出来的纹理也会是固定的,但又具备随机的视觉效果。
常用的白噪声随机产生函数如下:
float random = dot(vec in , vec const);
使用输入向量和一个任意向量点乘,即可得到一个随机的结果;
在OpenGL中使用:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; float rand(vec2 vec){ float random = dot(vec , vec2(-12.000,20.820)); random = fract(random); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand(uv)); gl_FragColor = vec4(color,1.0); }
得到的噪声图:
可以看到两个向量点乘投影形成的条带。一点也不均匀。
解决方法是对点积后的结果取正弦,然后乘上一个大数,将其小数点移后几位,得到更为随机分布的小数。
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; float rand(vec2 vec){ float random = sin(dot(vec, vec2(12.9898,78.233))) * 43758.5453; random = fract(random); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand(uv)); gl_FragColor = vec4(color,1.0); }
得到的噪声如下:
注意这里取的
vec2(12.9898,78.233)
和43758.5453;
都是前辈们总结下来的经验数,可以得到很好的白噪声图。
二、细胞
分割为细胞的方法就是纹理坐标乘上对应的倍数,简单高效。
void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; uv = uv * 10.0; vec3 color = vec3(fract(uv),1.0); gl_FragColor = vec4(color,1.0); }
得到的效果图:
为每个细胞赋上不同的颜色:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; float rand2dTo1d(vec2 value ,vec2 randvec2 ) { vec2 dotDir = randvec2; vec2 smallValue = sin(value); float random = dot(smallValue, dotDir); random = fract(sin(random) * 143758.5453); return random; } vec2 rand2dto2d(vec2 value){ return vec2( rand2dTo1d(value, vec2(12.989, 78.233)), rand2dTo1d(value, vec2(39.346, 11.135)) ); } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec2 st = uv * 10.0; vec3 color = vec3(rand2dto2d(floor(st)),1.0); gl_FragColor = vec4(color,1.0); }
得到的图片:
这里使用了前面提到的白噪声公式来为细胞产出不同的颜色。
三、连续噪声(Value Noise)
白噪声很好,但是它不具备连续性。
柏林噪声可以很好地解决这个问题。
使用一维噪声:
使用一维的白噪声生成灰度图
float rand1dTo1d(float value){ float mutator = 0.546; float random = fract(sin(value + mutator) * 143758.5453); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand1dTo1d( floor(uv.x * 30.))); gl_FragColor = vec4(color,1.0); }
得到:
将其压缩成线:
float rand1dTo1d(float value){ float mutator = 0.546; float random = fract(sin(value + mutator) * 143758.5453); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; //移到中心 uv -= vec2(0.5); vec2 st = uv * 6.; float noise = rand1dTo1d( floor(st.x));
//取噪声与纹理y坐标距离
//在 dist-> 0时画线, float dist = abs(noise - st.y);
//抗锯齿 vec3 color = vec3(smoothstep(0, fwidth(st.y), dist)); gl_FragColor = vec4(color,1.0); }
得到:
为了让值连续,取其小数部分进行插值,修改noise如下:
//插值 float noise = mix(rand1dTo1d( floor(st.x)), rand1dTo1d(ceil(st.x)), fract(st.x));
得到的图如下:
这时使用的插值因子是
st.x
得到的结果并不平滑,我们应该使用缓动公式插值,常见的公式是。
更改,如下:
//插值 float x = fract(st.x); float t= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.); float noise = mix(rand1dTo1d( floor(st.x)), rand1dTo1d(ceil(st.x)), fract(t));
得到的图如下:
此时,一个连续的一维的噪声就生成了。
下面应用到二维:
一张纹理图片中,直观输入就是uv,一个vec2变量。
在一维情况下,我们需要考虑左右插值,仅需插值一次。
二维时,取输入点记作(x0,y0),则需要使用(x0+1,y0),(x0,y0+1),(x0+1,y0+1)共四个点计算噪声值。所以,噪声函数应该变为:
float noise2d(vec2 value){ float x10 = rand2dTo1d(vec2(floor(value.x), ceil(value.y)), vec2(12.989, 78.233)); float x11 = rand2dTo1d(vec2(ceil(value.x), ceil(value.y)), vec2(12.989, 78.233)); float x00 = rand2dTo1d(vec2(floor(value.x), floor(value.y)), vec2(12.989, 78.233)); float x01 = rand2dTo1d(vec2(ceil(value.x), floor(value.y)), vec2(12.989, 78.233)); float x = fract(value.x); float y = fract(value.y);
float tx= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.); float ty= 6.*pow(y,5.) - 15.*pow(y,4.)+10.*pow(y,3.); //使用 tx插值 x -> x+1 float x1 = mix(x10, x11, tx); float x0 = mix(x00, x01, tx);
//使用ty插值 y -> y+1 float noise = mix(x0, x1, ty); return noise; }
主函数调用变为:
void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; //移到中心 uv -= vec2(0.5); vec2 st = uv * 10.; //插值 float noise = noise2d(st); vec3 color = vec3(noise); gl_FragColor = vec4(color,1.0); }
这时得到的噪声图如下:
一个连续的噪声图。
三维的情况同样,只不过插值点变成了八个。
四、柏林噪声
价值噪声使用内插值的方式实现了连续效果,但是情况不理想,更多时候采用梯度插值的方式,也就是柏林噪声,这样得到的噪声更加平滑连续。
一维柏林噪声实现
float gradientNoise(float value){
float x =fract(value);
float tx= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.);
float noise0 = (rand1dTo1d(floor(value)) * 2. - 1.) * x;
float noise1 = (rand1dTo1d(ceil(value)) * 2. - 1.)*(x -1.);
return mix(noise0, noise1, tx);
}
柏林噪声与价值噪声区别就在噪声产生的方式,rand1dTo1d(floor(value)) * 2. - 1.操作会产生(-1,+1)的随机变量,在一维情况下可视为随机长度的上下梯度。
在(x,x+1)的整数之间,由于伪随机数的关系,产生随机值是固定的,因此,乘上小数部分后,即可得到连续的梯度。
一维柏林噪声结果:
二维柏林噪声
取(x,y)(x+1,y)(x,y+1)(x+1,y+1)四个正方形顶点,产生四个向量,并与指向正方形内部的方向向量作点积,得到各个顶点在正方形上的投影值,插值即可得到。
a = dot(random( i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ) b = dot( random( i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ) c = dot( random( i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ) d = dot( random( i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ) //a b 、c d 在x轴连续 ab= mix(a,b,u.x) cd = mix(b,c,u.x) //ab cd 在y轴连续 noise = mix(ab,cd,u.y)
得到:
加亮颜色值
col += 0.5;
让明暗分界更加明显:
col = fract(col *10.);
col*10得到的值范围是(0,10),取小数后得到十个(0,1)的数值范围,而且根据噪声的插值方式,这十个数值范围应该是连续且嵌套的,得到类似等高线的效果:
五、噪声叠加
在一个噪声基础上叠加多种频率的噪声,可以得到变化丰富的噪声图:
0次叠加
叠加1/2频率 +1/2 的noise
(1+1/2+1/4)*noise
在叠加的基础上加上UV偏移
uv =rotateMatrix * uv