欢迎继续一起踏上 The Journey of Chaos, 关于 Shader 生成技术的一些基础性学习。噪声会分为以下几篇内容学习
-
CurlNoise(本篇)
-
CurlNoise调节
-
CurlNoise 计算优化 Bitangent Noise
-
FBM深入
再上一篇中我们理解了 CurlNoise的方法,在本篇我们将继续追着 www.cs.ubc.ca/~rbridson/d… 对其中的控制 curl的方法进行讨论和理解。
一些物理概念
1. 势能(Potential Energy)
高山上的落石滚落下来时能够砸出一个大坑,蓄能水电站水库中的水从高处被释放能够带动发动机发电,紧绷的弓弦能够把箭射出去。你看,深处高处的石头、水流和紧绷的弓弦都具有能量。这种能量是什么?势能(Potential energy)是体系内因物体间的相对位置或状态而储存的能量,它能够释放或转化为其他形式的能量,如动能。势能是一个状态量,也称为位能。在流体力学中,势能通常指流体的重力势能或其他形式的势能(如压力势能)。势能与流体的高度、压力分布等相关,但在描述速度场时,通常不直接使用势能,而是通过势函数或流函数间接体现。
2. 势函数 (Velocity Potential, ϕ)
势函数 是用于描述**无旋流动(Irrotational Flow)**的标量函数。 在无旋流动中,速度场
可以表示为势函数的梯度:
这意味着速度场的旋度为零:
势函数常用于描述理想流体(无粘性、无旋)的运动。
3. 流函数(Stream Function)
-流函数 是用于描述**不可压缩流动(Incompressible Flow)**的标量函数。在二维不可压缩流动中,速度场
可以表示为流函数的偏导数:
这意味着流函数满足连续性方程(质量守恒):
流函数的等值线表示流线,即流体微团的运动轨迹。也就是我们特效中的线
4. 速度场(Velocity)
速度场 是描述流体运动的核心物理量,表示流体微团在空间中的运动方向和速率。 在无旋流动中,速度可以通过势函数的梯度计算:
在不可压缩流动中,速度可以通过流函数的旋度计算(二维情况下):
其中 是垂直于二维平面的单位向量。
5. 势函数与流函数的关系
在二维无旋且不可压缩流动中,势函数 和流函数
满足柯西-黎曼条件:
这意味着势函数和流函数是共轭调和函数,可以通过复变函数理论联系起来。听起来很复杂对不对。不过我们看其物理意义就会很清楚。
-
势函数
的等值线

论文中给出了上做图图,在流线中,有这么一块里面没有流线,同时源的周围有一圈流线保证了整体无散。同时论文中并给出下列公式
如果需要明白上述公式,需要结合上面的物理概念基础知识,对公式每一项含义有了解。 在你描述的情境中,是一个二维噪声函数,其中:
-
**噪声函数
**: 这是一个随时间
和空间位置
变化的噪声函数, N也就是我们 simplex Noise. x也就是 UV坐标
-
调制函数
: 这是一个平滑的阶跃函数,基于鼠标光标与位置
的距离。它的作用是调整噪声的强度,使得靠近鼠标光标的位置噪声更强,远离鼠标光标的位置噪声更弱。这里调节的其实就是 N生成向量场的大小
-
边界距离函数
: 这是位置
到最近固体边界的距离。用于确保在靠近边界时速度场逐渐减弱,避免在边界处产生不合理的速度。
-
斜坡函数
: 这是一个基于边界距离的斜坡函数,
是一个参考距离。当
小于
时,函数值逐渐减小到零,确保在边界附近速度场平滑过渡到零。其实就是个
smoothstep -
势函数
: 也就是论文中提出的公式,也是上面各个函数的组合
知道原理之后就非常简单,代码就只有一行,修改 noise函数,做一个 smoothstep
float boundary_sdf(vec2 pos)
{
return length(pos -vec2(0.2)) - 0.3;
}
float main_noise(in vec2 p)
{
float noise = simplex_noise(p);
float scaleSize = 0.1;
float boundaryFactor = smoothstep(0., 1.0, boundary_sdf(p) / scaleSize) ;
return noise * boundaryFactor;
}
下面我在 (0.2, 0.2)位置做了一个 0.3 半径的圆,
控制速度场
目前所有的streamline都是通过随机数生成速度场画出来的,但是由于旋度恒等式,我们在每一帧都已经满足了这个条件
那么意味着我们对每一帧的速度场做一些改变,也不会影响到整体流场散度为 0 的特点。这也是上一篇埋的彩蛋,如何通过鼠标让流线移动。 这里也许有非常多的效果。
上图为随着鼠标移动产生了旋转的 streamline
vec2 ms = (iMouse.xy * 2.0 - iResolution.xy) / iResolution.y;
vec2 mv = (ms - p0) * 0.5;
mv /= dot(mv, mv) + 0.04;
mv = vec2(-mv.y, mv.x);
v0 += mv * 0.2;
主要是在计算粒子运动方向的时候增加了一个垂直方向的力。 以上代码具体解释
-
vec2 mv = (m -
s - p0) * 0.5;计算出鼠标与渲染点的向量。 -
mv /= dot(mv, mv) + 0.04;计算出越远受到的影响越小,同时加上 0.04避免除 0 -
mv = vec2(-mv.y, mv.x);将向量旋转 90 度,实现垂直力,从而产生漩涡
而只要注释掉,mv = vec2(-mv.y, mv.x); 。 就可以产生如下吸引的效果 
Web中使用Curl


对比上左右的效果,右侧粒子发散出去后路径是扭曲的曲线,但是扭曲的幅度控制在一定范围。 这里实际上就是用 curlNoise为粒子运动叠加了一个速度。 核心代码非常简单。 以下是 js代码。 来自于 github.com/bobbyroe/cu…
```
function update(t) {
particles.forEach((p) => {
p.update(t);
p.render(ctx);
});
let angle = Math.random() * Math.PI * 2;
let speed = Math.random() * 3 + 1;
let curl = getCurl(t, 0, 0);
let pOptions = {
pos: { x: SCREEN_WIDTH * 0.5, y: SCREEN_HEIGHT * 0.5 },
vel: { x: Math.cos(angle) * speed, y: Math.sin(angle) * speed },
col: { hue: 360 * curl.x, lightness: 100, alpha: 1.0 },
fadeRate: 0.005,
};
let particle = getParticle(pOptions);
particles.push(particle);
while (particles.length > maxParticles) {
particles.shift();
}
}
return { update };
}
```