【译】创意编码之噪音
【译】创意编码之噪音
原文:https://varun.ca/noise/
作者:Ahmad Shadeed
译者:NSGUF
噪音是一种创意编码中不可或缺的工具。我们使用它来生成各种各样的仿自然形态的效果,如云,风景以及轮廓等;或者以更逼真的行为移动和扭曲对象;
表面上看,噪音似乎很容易使用,但是它有非常多层次,这篇文章深入研究了什么是噪音,它的变种有哪些,如何在网络和应用程序上使用;也会展示很多的例子,非常多的例子!
噪音是什么?
假设你想在屏幕上移动一个物体。动画师会使用关键帧和补帧来描述这个确切的运动。生成类似这个动画师的艺术家依赖于算法;
那么,math.random()
可以实现吗?
不行,随机就太不自然了,看下面这个粉红色的球,到处弹跳,这就很恶心了🥴;
我们需要更平滑,更自然的随机性,这个(黄色的球)就是噪音函数生成的,美观多了;
自然随机的想法在创意编码中出现了一遍又一遍。例如:我们经常使用噪音函数生成纹理,移动或扭曲物体;
在网络上生成噪音
两种类型噪音——Perlin和Simplex
在1980年初期,Ken Perlin在做Tron时开发了Perlin噪音并因这个特效获得奥斯卡奖。然后他在此这种特效上改进开发了Simplex,这种新算法运行速度更快。我将会专注于后者,并在我的例子中使simplex-noise库;
噪音函数接受两个输入(用于生成输出)并且返回一个-1到1的值。这个输出结合了Math.sin()
和Math.random()
,随机波就是我描述他的方式;
import SimplexNoise from 'simplex-noise';
const simplex = new SimplexNoise();
const y = simplex.noise2D(x * frequency, 0) * amplitude;
基础力学和波浪的工作原理相似,你可以通过调整频率和振幅来控制波浪的速度和幅度;
emm,二维噪音,发生了什么?
噪音维度
噪音算法可以实现多维度。这里是将维度视为噪音生成器的输入数量,2个输入为2D,三个是3D,以此类推;
选择哪种维度,取决于使用的生成器的变量,让我们看几个例子:
💡 从这里开始,每个例子都会有个通过指向实际源码嵌入的源码卡片,在某些情况下,你可以调整变量;(翻译我是使用的gif)
一维噪音 —— 波动
从技术上讲,是没有一维噪音的。你可以通过将0作为第二个参数给到simplex noise2D得到1维噪音;架设你想让这个移动的球看起来比较自然,可以增加x坐标并生成y坐标;
x += 0.1;
y = simplex.noise2D(x * frequency, 0) * amplitude;
二维噪音 —— 地形生成器
我们可以通过移动z轴方向的顶点,将一个水平2D平面转换成丘陵地形,使用x,y轴坐标来生成z坐标位置;
就这样,我们有了一个地形生成器🏔️;
z = simplex.noise2D(x * frequency, y * frequency) * amplitude;
三维噪音——顶点位移
你可能已经见过这种自然扭曲的球体,它们通过移动球体顶点生成;使用顶点坐标(x,y,z)
来生成扭曲的位置变量,然后通过移动顶点生成放射的形状;
const distortion =
simplex.noise3D(x * frequency, y * frequency, z * frequency) * amplitude;
newPosition = position.normalize().multiplyScalar(distortion);
四维噪音—— 动态扭曲效果
我们可以通过4维噪音来生成一个不停动态扭曲的球,输入分别是顶点坐标(x,y,z)
和时间
。这项技术常用于创造类似火球的效果;
const distortion =
simplex.noise4D(
x * frequency,
y * frequency,
z * frequency,
time * frequency
) * amplitude;
newPosition = position.normalize().multiplyScalar(distortion);
在上面的大多数例子里我们都使用了振幅,这是程序中缩放噪音输出的方式,你还可以使用插值将噪音输出映射到选择的指定范围;
现在我们已经掌握了基础知识,让我们看一些噪音的其他应用;
纹理
使用(x,y)
坐标,生成二维的表格格式的噪音,看起来像这样:
使用方式:
for (let y = 0; y < gridSize[1]; y++) {
for (let x = 0; x < gridSize[0]; x++) {
const n = simplex.noise2D(
x / (gridSize[0] * 0.75),
y / (gridSize[1] * 0.75)
);
}
}
这是我们的出发点。
我们可以使用Marching Squares算法将2D数据转换成等高线。使用Canvas,SVG,WebGL或者其他你喜欢的工具,我最近使用SVG来生成带有轮廓的卡片,这方面相关的完整教程,请查看https://storybook.js.org/blog/generative-image-service/📸;
关于噪音,最酷的事情是你可以添加时间维度,来给你的图片添加动画;
simplex.noise3D(x / (gridSize[0] * 0.75), y / (gridSize[1] * 0.75), time);
场
老实讲,噪音的二维数据是非常实用的,这可以做很多的事情,其中一个是我特别喜欢——噪音领域;
让我们回到最初的灰度输出,这里可以映射出一个更有趣的色阶,你可以获取到等离子;
或者从矩形网格开始,通过使用噪音来控制颜色和角度,将其变成向量场;
噪音粒子
向量场很酷,但是流场更让人兴奋。这是我去年画的图;
注意笔的轨迹。每一划都是通过将粒子放到向量场上来生成的。然后追踪它的路径。💫
好吧,让我们分解这个过程。
创建流场
第一步,创建一个向量场。和之前一样。但这一次,我们不会为向量场设置动画。
第 2 步,将一堆粒子放到画布上。它们的运动方向基于底层向量场。向前迈出一步,找到新的方向并重复。
function moveParticle(particle) {
// Calculate direction from noise
const angle =
simplex.noise2D(particle.x * FREQUENCY, particle.y * FREQUENCY) * AMPLITUDE;
// Update the velocity of the particle
// based on the direction
particle.vx += Math.cos(angle) * STEP;
particle.vy += Math.sin(angle) * STEP;
// Move the particle
particle.x += particle.vx;
particle.y += particle.vy;
// Use damping to slow down the particle (think friction)
particle.vx *= DAMPING;
particle.vy *= DAMPING;
particle.line.push([particle.x, particle.y]);
}
第三步:追踪粒子的位置,以追踪其路径;
添加更多的粒子和颜色,我们就可以获得流场;
粒子系统
说到粒子,噪声也出现在粒子系统中。对于像雨、雪或五彩纸屑等效果,你希望模拟一个看起来很自然的运动。想象一个五彩纸屑颗粒飘向地面。粒子不会直线移动。由于空气阻力,它们会漂浮和摆动。噪声是添加仿自然形态非常好的工具。
const wiggle = {
x: simplex.noise2D(particle.x * frequency, time) * amplitude,
y: simplex.noise2D(particle.y * frequency, time) * amplitude,
};
particle.x += wiggle.x;
particle.y += wiggle.y;
想深入了解,详情请看:https://varun.ca/confetti/
着色器
让我们回到那些动画斑点。使用着色器操作和设置3D动画几何图形的效率要高得多。但是着色器本身是一个完全不同的世界。对于初学者来说,有一个特殊的 WebGL 版本的噪音,gsl-noise。我们需要使用 glslify 将它导入到我们的着色器中。
我们会慢慢来,先从 2D 开始。
上面的动画与我们之前看到的轮廓非常相似。它是通过片段着色器实现的。这意味着我们为canvas的每个像素运行相同的程序。
注意到pragma
行了吗?那是我们导入 webgl-noise。然后用它为每个像素位置 vUv
生成噪声数据。
这部分现在看起来应该很熟悉了。
fragment.glsl
precision highp float;
uniform float time;
uniform float density;
varying vec2 vUv;
#define PI 3.141592653589793
#pragma glslify: noise = require(glsl-noise/simplex/3d);
float patternZebra(float v){
float d = 1.0 / density;
float s = -cos(v / d * PI * 2.);
return smoothstep(.0, .1 * d, .1 * s / fwidth(s));
}
void main() {
// Generate noise data float amplitude = 1.0; float frequency = 1.5; float noiseValue = noise(vec3(vUv * frequency, time)) * amplitude; // Convert noise data to rings
float t = patternZebra(noiseValue);
vec3 color = mix(vec3(1.,0.4,0.369), vec3(0.824,0.318,0.369), t);
// Clip the rings to a circle
float dist = length(vUv - vec2(0.5, 0.5));
float alpha = smoothstep(0.250, 0.2482, dist);
gl_FragColor = vec4(color, alpha);
}
片段着色器具有用于绘制形状的距离函数的概念。 patternZebra 就是一个将噪声数据转换为环的示例。本质上,它返回 0 或 1。然后我们用它来选择混合的颜色。最后,使用另一个距离函数来剪裁圆形边界。
看起来很基础?
好吧,着色器的惊人之处在于,你可以将它们变成一种材质并将其应用于更复杂的几何体。
噪声材质
我们可以从上面获取相同的 2D mars 着色器。将其转换为 ThreeJS 着色器材质。然后使用顶点位置 vPosition 代替 vUv 生成噪声。 瞧,我们有一个 3D mars!
const material = new THREE.ShaderMaterial({
extensions: {
derivatives: true,
},
uniforms: {
time: { value: 0 },
density: { value: 1.0 },
},
vertexShader: /*glsl*/ ` varying vec3 vPosition; void main () { vPosition = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `,
fragmentShader: glslify(/* glsl */ ` precision highp float; varying vec3 vPosition; uniform float time; uniform float density; #pragma glslify: noise = require(glsl-noise/simplex/4d); #define PI 3.141592653589793 float patternZebra(float v){ float d = 1.0 / density; float s = -cos(v / d * PI * 2.); return smoothstep(.0, .1 * d, .1 * s / fwidth(s)); } void main () { float frequency = .6; float amplitude = 1.5; float v = noise(vec4(vPosition * frequency, sin(PI * time))) * amplitude; float t = patternZebra(v); vec3 fragColor = mix(vec3(1.,0.4,0.369), vec3(0.824,0.318,0.369), t); gl_FragColor = vec4(fragColor, 1.); } `),
});
老实说,这只是一个开始。着色器开辟了许多可能性。就像下面的例子
不仅是噪声映射到颜色,还可以是透明度。
光泽斑点
与顶点置换背后的想法非常相似。我们编写了一个顶点着色器来转换球体的每个顶点,而不是片段着色器。同样,使用着色器材质应用。
vertex.glsl
precision highp float;
varying vec3 vNormal;
#pragma glslify: snoise4 = require(glsl-noise/simplex/4d)
uniform float u_time;
uniform float u_amplitude;
uniform float u_frequency;
void main () {
vNormal = normalMatrix * normalize(normal); float distortion = snoise4(vec4(normal * u_frequency, u_time)) * u_amplitude; vec3 newPosition = position + (normal * distortion);
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
加上一些 matcap,你就会得到一些有光泽的动画斑点。
轮到你创造噪音了
那真是一段旅程。运动、纹理、场、粒子系统和置换——噪声都能做。它确实是创意编码世界的主力军。
很明显,你想玩噪音😁
我已经为你准备了一个小小的入门 CodePen。它旋转了一个噪声网格并将其映射到一个字形数组。只需从修改数组开始,看看你能走多远。
const glyphs = ['¤', '✳', '●', '◔', '○', '◕', '◐', '◑', '◒'];
或者,如果您正在寻找更高级的东西,请查看noisedeck.app。它就像一个只用于视觉效果的合成器。