随机
前面介绍的一些图案生成思路都是通过确定的数学函数加上周期重复来实现的,而大自然中有很多现象几乎是无序的,要模拟这些无序的自然现象,就要用到随机。
在JavaScript中,Math.random
返回0到1之间的随机数,但对着色器来说没有现成的随机函数,只能通过人工来构建随机函数。
为了说明构造随机的原理,我们暂时先把WebGL放一放,先用JavaScript,在Canvas2D上绘图来看一下。
const canvas = document.querySelector('canvas');
const width = 400 * window.devicePixelRatio;
const height = 400 * window.devicePixelRatio;
canvas.width = width;
canvas.height = height;
const random = Math.random;
function drawRandomPixel(ctx) {
const [w, h] = [random() * width, random() * height];
ctx.fillRect(w, h, 1, 1);
}
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
for(let i = 0; i < 0.25 * width * height; i++) {
drawRandomPixel(ctx);
}
如上面代码所示,我们使用在画布上打随机点的方式来绘制一张图片,通过random函数得到点的坐标,然后通过ctx.fillRect
将点绘制到画布上,这样我们得到了一张黑色噪点的图案。
这里我们默认是用JavaScript内置的Math.random
函数绘制的,现在我们要尝试自己实现Math.random
函数,我们来看一下:
const canvas = document.querySelector('canvas');
const width = 400 * window.devicePixelRatio;
const height = 400 * window.devicePixelRatio;
canvas.width = width;
canvas.height = height;
function fract(num) {
return Math.abs(num % 1);
}
let seed = 0;
function random() {
return fract(Math.sin(seed++) * 1e5);
}
function drawRandomPixel(ctx) {
const [w, h] = [random() * width, random() * height];
ctx.fillRect(w, h, 1, 1);
}
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
for(let i = 0; i < 0.25 * width * height; i++) {
drawRandomPixel(ctx);
}
我们修改了random函数,用:
let seed = 0;
function random() {
return fract(Math.sin(seed++) * 1e5);
}
代替了Math.random
,这里我们用fract函数取sin函数的小数部分,而把sin函数乘以1e5是为了截取到小数点后5位之后的部分。
这是因为,sin函数显然不是随机的,但是它的小数点后N位之后的值,由于浮点数精度原因,呈现出随机性。
你把这里的Math.sin换成Math.cos或Math.tan也可以得到类似的结果,但也不是所有的函数都可以,这和函数曲线有关,比如Math.log就不行。
Shader的常用伪随机函数
理解了伪随机原理,我们来看一下shader中的常用伪随机函数:
highp float random(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);
}
原理上,和我们上面取sin函数小数点后几位的道理是一样的,只不过把seed换成二维向量,用的参数值让随机分布更均匀。
这样,我们就可以用随机函数来绘图了。
比如我们可以给菱形网格赋予随机颜色:
还有下面这个著名的10 PRINT CHR$(205.5+RND(1)); : GOTO 10迷宫生成器也是通过伪随机数来实现的:
最后,留给大家一个问题:
因为random
是伪随机函数,意味着我们每次运行上面的代码,绘制出的颜色网格和迷宫形状是一样的,我们能像JavaScript的random那样,每次运营的时候,让它绘制出不一样的随机网格颜色和随机迷宫形状吗?
如果你想到了该怎么做,可以在码上掘金里修改上面的代码,实现效果,然后将它分享到评论区。