Curl noise
Пример curl noise с элементами в виде коробок. Используются шейдеры и webGL библиотека Kgl
HTML
<script type="x-shader/x-fragment" id="reset-velocity">
precision highp float;
void main () {
gl_FragColor = vec4(vec3(0.), 1.);
}
</script>
<script type="x-shader/x-fragment" id="reset-position">
precision highp float;
uniform vec2 size;
// https://github.com/mattdesl/glsl-random
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);
}
void main () {
vec2 nPosition = gl_FragCoord.st / size * 2. - 1.;
vec4 position = vec4(
nPosition * size,
0.,
random(nPosition)
);
gl_FragColor = position;
}
</script>
<script type="x-shader/x-fragment" id="velocity">
precision highp float;
uniform vec2 size;
uniform sampler2D prevVelocityTexture;
uniform sampler2D prevPositionTexture;
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x*34.0)+1.0)*x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v)
{
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
// x0 = x0 - 0.0 + 0.0 * C.xxx;
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y
// Permutations
i = mod289(i);
vec4 p = permute( permute( permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
float n_ = 0.142857142857; // 1.0/7.0
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
//Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
dot(p2,x2), dot(p3,x3) ) );
}
// https://www.npmjs.com/package/glsl-curl-noise
vec3 snoiseVec3( vec3 x ){
float s = snoise(vec3( x ));
float s1 = snoise(vec3( x.y - 19.1 , x.z + 33.4 , x.x + 47.2 ));
float s2 = snoise(vec3( x.z + 74.2 , x.x - 124.5 , x.y + 99.4 ));
vec3 c = vec3( s , s1 , s2 );
return c;
}
vec3 curlNoise( vec3 p ){
const float e = .1;
vec3 dx = vec3( e , 0.0 , 0.0 );
vec3 dy = vec3( 0.0 , e , 0.0 );
vec3 dz = vec3( 0.0 , 0.0 , e );
vec3 p_x0 = snoiseVec3( p - dx );
vec3 p_x1 = snoiseVec3( p + dx );
vec3 p_y0 = snoiseVec3( p - dy );
vec3 p_y1 = snoiseVec3( p + dy );
vec3 p_z0 = snoiseVec3( p - dz );
vec3 p_z1 = snoiseVec3( p + dz );
float x = p_y1.z - p_y0.z - p_z1.y + p_z0.y;
float y = p_z1.x - p_z0.x - p_x1.z + p_x0.z;
float z = p_x1.y - p_x0.y - p_y1.x + p_y0.x;
const float divisor = 1.0 / ( 2.0 * e );
return normalize( vec3( x , y , z ) * divisor );
}
const float speed = 2.;
const float density = 0.007;
const float ease = 0.02;
void main () {
vec2 uv = gl_FragCoord.st / size;
vec4 velocity = texture2D(prevVelocityTexture, uv);
vec3 prevPosition = texture2D(prevPositionTexture, uv).xyz;
velocity.xyz += curlNoise(prevPosition * density) * speed;
velocity.xyz *= velocity.w;
if (velocity.w > 0.01) {
velocity.w -= velocity.w * ease;
} else {
velocity.w = 1.;
}
gl_FragColor = velocity;
}
</script>
<script type="x-shader/x-fragment" id="position">
precision highp float;
uniform vec2 size;
uniform sampler2D prevPositionTexture;
uniform sampler2D velocityTexture;
void main () {
vec2 uv = gl_FragCoord.st / size;
vec4 prevPosition = texture2D(prevPositionTexture, uv);
vec4 velocity = texture2D(velocityTexture, uv);
vec3 position;
if (velocity.w == 1.) {
vec2 nPosition = uv * 2. - 1.;
position = vec3(nPosition * size, 0.);
} else {
position = prevPosition.xyz + velocity.xyz;
}
gl_FragColor = vec4(position, prevPosition.w);
}
</script>
<script type="x-shader/x-vertex" id="main-vs">
attribute vec3 position;
attribute vec3 normal;
attribute vec2 instancedUv;
uniform sampler2D positionTexture;
uniform sampler2D velocityTexture;
uniform mat4 mvpMatrix;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec3 ambientColor;
uniform vec3 eyeDirection;
uniform float time;
varying vec4 vColor;
// https://github.com/yuichiroharai/glsl-y-rotate#readme
mat3 rotateQ(vec3 axis, float rad) {
float hr = rad / 2.0;
float s = sin( hr );
vec4 q = vec4(axis * s, cos( hr ));
vec3 q2 = q.xyz + q.xyz;
vec3 qq2 = q.xyz * q2;
vec2 qx = q.xx * q2.yz;
float qy = q.y * q2.z;
vec3 qw = q.w * q2.xyz;
return mat3(
1.0 - (qq2.y + qq2.z), qx.x - qw.z, qx.y + qw.y,
qx.x + qw.z, 1.0 - (qq2.x + qq2.z), qy - qw.x,
qx.y - qw.y, qy + qw.x, 1.0 - (qq2.x + qq2.y)
);
}
// https://github.com/mattdesl/glsl-random
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);
}
vec3 hsv(float h, float s, float v) {
vec4 t = vec4(1., 2. / 3., 1. / 3., 3.);
vec3 p = abs(fract(vec3(h) + t.xyz) * 6. - vec3(t.w));
return v * mix(vec3(t.x), clamp(p - vec3(t.x), 0., 1.), s);
}
const float PI = 3.1415926;
const float PI2 = PI * 2.;
const float colorInterval = PI2 * 10.;
const float scale = 2.;
const float maxScaleRate = 1.4;
const float rotationSpeed = 100.;
const float minRotationSpeed = 0.1;
void main () {
vec3 modelPosition = position;
vec4 instancedPosition = texture2D(positionTexture, instancedUv);
float randomValue = instancedPosition.w;
float velocity = texture2D(velocityTexture, instancedUv).w;
float life = smoothstep(0.01, 0.04, velocity);
float cScale = scale;
cScale *= life;
cScale *= mix(1., maxScaleRate, (instancedPosition.z - 1.) * 0.01);
modelPosition *= 0.5 * cScale;
vec3 axis = normalize(vec3(
random(vec2(randomValue, 0.)),
random(vec2(0., randomValue)),
random(vec2(randomValue, 1.))
));
float radian = PI2 * random(vec2(randomValue));
radian += velocity * mix(minRotationSpeed, rotationSpeed, randomValue);
mat3 rotate = rotateQ(axis, radian);
modelPosition *= rotate;
vec3 cNormal = normalize(normal);
cNormal *= rotate;
vec3 invLight = normalize(invMatrix * vec4(lightDirection, 0.)).rgb;
vec3 invEye = normalize(invMatrix * vec4(eyeDirection, 0.)).rgb;
vec3 halfLE = normalize(invLight + invEye);
float diffuse = clamp(dot(cNormal, invLight), 0.1, 1.);
float specular = pow(clamp(dot(cNormal, halfLE), 0., 1.), 50.);
float colorNTime = mod(time * 0.001, colorInterval) / colorInterval;
float alpha = 1.;
vColor = vec4(hsv(colorNTime * PI2, 0.25 + 0.7 * colorNTime, 0.85 + 0.1 * colorNTime), alpha);
vColor.rgb *= vec3(diffuse + specular);
vColor.rgb += ambientColor;
vColor.rgb *= mix(0.2, 1., clamp(((instancedPosition + 100.) / 200.).z, 0., 1.));
vColor.rgb *= life;
gl_Position = mvpMatrix * (vec4(modelPosition + instancedPosition.xyz, 1.));
}
</script>
<script type="x-shader/x-fragment" id="main-fs">
precision highp float;
varying vec4 vColor;
void main () {
gl_FragColor = vColor;
}
</script>
<script type="x-shader/x-fragment" id="texture">
precision highp float;
uniform vec2 resolution;
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, gl_FragCoord.st / resolution);
}
</script>
CSS
html,
body {
height: 100%;
}
body {
overflow: hidden;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
JS
webGL библиотекаhttps://unpkg.com/@ko-yelie/kgl@0.2.1/dist/index.iife.min.js
и скрипт// using original library https://github.com/ko-yelie/kgl
const width = 100
const height = 100
const sizeUniform = [width, height]
const particleUv = []
for (let j = 0; j < height; j++) {
for (let i = 0; i < width; i++) {
particleUv.push(i / (width - 1), 1 - j / (height - 1))
}
}
const webgl = new Kgl({
cameraPosition: [0, 0, 480.],
fov: 50,
programs: {
resetVelocity: {
fragmentShaderId: 'reset-velocity',
isFloats: true,
},
resetPosition: {
fragmentShaderId: 'reset-position',
uniforms: {
size: sizeUniform,
},
isFloats: true,
},
velocity: {
fragmentShaderId: 'velocity',
uniforms: {
size: sizeUniform,
prevVelocityTexture: 'framebuffer',
prevPositionTexture: 'framebuffer',
},
isFloats: true,
},
position: {
fragmentShaderId: 'position',
uniforms: {
size: sizeUniform,
prevPositionTexture: 'framebuffer',
velocityTexture: 'framebuffer',
},
isFloats: true,
},
main: {
vertexShaderId: 'main-vs',
fragmentShaderId: 'main-fs',
attributes: {
position: {
value: [1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
size: 3,
},
normal: {
value: [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],
size: 3
},
indices: {
value: [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23],
isIndices: true
},
},
instancedAttributes: {
instancedUv: {
value: particleUv,
size: 2
}
},
uniforms: {
positionTexture: 'framebuffer',
velocityTexture: 'framebuffer',
time: 0,
},
isDepth: true,
isTransparent: true,
},
texture: {
fragmentShaderId: 'texture',
uniforms: {
texture: 'framebuffer'
},
clearedColor: [0, 0, 0, 1],
isTransparent: true
}
},
framebuffers: [
'scene',
],
framebufferFloats: {
velocity0: {
width,
height
},
velocity1: {
width,
height
},
position0: {
width,
height
},
position1: {
width,
height
}
},
isAutoStart: false
})
let loopCount = 0
let targetbufferIndex
let prevbufferIndex
let i
targetbufferIndex = loopCount++ % 2
{
webgl.bindFramebuffer('velocity' + targetbufferIndex)
webgl.programs['resetVelocity'].draw()
}
{
webgl.bindFramebuffer('position' + targetbufferIndex)
webgl.programs['resetPosition'].draw()
}
const draw = time => {
targetbufferIndex = loopCount++ % 2
prevbufferIndex = 1 - targetbufferIndex
{
webgl.bindFramebuffer('velocity' + targetbufferIndex)
webgl.programs['velocity'].draw({
prevVelocityTexture: 'velocity' + prevbufferIndex,
prevPositionTexture: 'position' + prevbufferIndex,
})
}
{
webgl.bindFramebuffer('position' + targetbufferIndex)
webgl.programs['position'].draw({
prevPositionTexture: 'position' + prevbufferIndex,
velocityTexture: 'velocity' + targetbufferIndex,
})
}
{
webgl.bindFramebuffer('scene')
webgl.programs['main'].draw({
positionTexture: 'position' + targetbufferIndex,
velocityTexture: 'velocity' + targetbufferIndex,
time,
})
}
{
webgl.unbindFramebuffer()
webgl.programs['texture'].draw({
texture: 'scene',
})
}
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)