Фосфоресценция
Светящиеся частицы на THREE.js и шейдерах. Продолжение экспериментов с частицами.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script id="vertexShaderParticle" type="x-shader/x-vertex">
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D u_noise;
attribute vec2 reference;
uniform sampler2D texturePosition;
uniform bool u_clicked;
varying float v_op;
float random(vec2 st) {
return fract(sin(dot(st,
vec2(12.9898,78.233)))*
43758.5453123);
}
void main() {
vec3 position = texture2D(texturePosition, reference).xyz;
position *= 3.;
// position -= 10.;
vec3 transformed = vec3( position );
vec4 mvpos = modelViewMatrix * vec4( transformed, 1.0 );
// gl_PointSize = 30.0 * (1.0 / (mvpos.z * mvpos.z));
// gl_PointSize = 1.;
// gl_PointSize = clamp(2. - length(transformed) * .01, 0., 2.);
gl_PointSize = random(reference) * 50. * (1. / length(mvpos.xyz) * 5.51);
v_op = 1. / length(position) * 8.;
// gl_PointSize = 2.;
gl_Position = projectionMatrix * mvpos;
}
</script>
<script id="fragmentShaderParticle" type="x-shader/x-fragment">
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D u_noise;
uniform bool u_clicked;
varying float v_op;
vec2 hash2(vec2 p)
{
vec2 o = texture2D( u_noise, (p+0.5)/256.0, -100.0 ).xy;
return o;
}
void main() {
// vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.x, u_resolution.y);
vec2 uv = gl_PointCoord.xy - .5;
vec3 particlecolour = vec3(.5, .53, .53) * 1.8;
vec3 outercolour = vec3(1.);
if(u_clicked) {
particlecolour = vec3(.05, .15, .2) * .5;
outercolour = vec3(0.);
}
float l = length(uv);
vec3 colour = mix(outercolour, particlecolour, smoothstep(.5, -.1, l));
colour = mix(vec3(2., 0.5, 0.), colour, smoothstep(3., 0.5, v_op));
gl_FragColor = vec4(colour, 1. - l * 2.);
}
</script>
<script id="fragmentShaderVelocity" type="x-shader/x-fragment">
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform float u_mousex;
varying float v_op;
// otaviogood's noise from https://www.shadertoy.com/view/ld2SzK
const float nudge = 0.739513; // size of perpendicular vector
float normalizer = 1.0 / sqrt(1.0 + nudge*nudge); // pythagorean theorem on that perpendicular to maintain scale
float SpiralNoiseC(vec3 p)
{
float n = 0.0; // noise amount
float iter = 1.0;
for (int i = 0; i < 8; i++)
{
// add sin and cos scaled inverse with the frequency
n += -abs(sin(p.y*iter) + cos(p.x*iter)) / iter; // abs for a ridged look
// rotate by adding perpendicular and scaling down
p.xy += vec2(p.y, -p.x) * nudge;
p.xy *= normalizer;
// rotate on other axis
p.xz += vec2(p.z, -p.x) * nudge;
p.xz *= normalizer;
// increase the frequency
iter *= 1.733733;
}
return n;
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 position = texture2D(v_samplerPosition, uv).xyz;
vec3 velocity = texture2D(v_samplerVelocity, uv).xyz;
vec3 acceleration = vec3(0.);
float l = length(position);
vec3 spherical = vec3(1./l, atan(position.y, position.x), acos(position.z / l));
float n = SpiralNoiseC(spherical * 6. + u_time);
n = SpiralNoiseC(vec3(l, spherical.y * 6. + u_time * 5., spherical.z));
// n = fract(n) * 3.;
// spherical *= 1. + n;
spherical.z += (1. / n-.5)*length(velocity);
spherical.y += n;
float a = n * .1 + smoothstep(5., 40., l) * 20.;
a += smoothstep(20., 0., l) * .3;
a -= smoothstep(30., 41., l) * 21.;
// spherical.x +=;
// spherical.x += smoothstep(20., 0., l) * .3;
// spherical.x -= smoothstep(30., 41., l) * 21.;
// spherical.xy += n;
// spherical.z *= 1.5;
// spherical.z += 1.;
// spherical.x -= smoothstep(5., 1., l) * 1.;
// spherical.yz += n*.5;
acceleration.x = spherical.x * sin(spherical.z) * cos(spherical.y) * a;
acceleration.y = spherical.x * sin(spherical.z) * sin(spherical.y) * a;
acceleration.z = spherical.x * cos(spherical.z) * a;
// if(acceleration.x == 0) { acceleration.x = .01 };
// acceleration *= acceleration * acceleration * 200.;
// acceleration = sin(acceleration) * .5 + .5;
// acceleration *= 100.;
vec3 vel = velocity * .98 + acceleration * .3;
if(length(vel) > 5.) {
vel = normalize(vel) * 5.;
}
gl_FragColor = vec4(vel, 1.0);
// gl_FragColor = vec4(-.1);
}
</script>
<script id="fragmentShaderPosition" type="x-shader/x-fragment">
uniform float delta;
uniform float u_time;
uniform sampler2D v_samplerPosition_orig;
uniform sampler2D u_noise;
vec3 hash3(vec2 p)
{
vec3 o = texture2D( u_noise, (p+0.5)/256.0, -100.0 ).xyz;
return o;
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 position_original = texture2D(v_samplerPosition_orig, uv).xyz;
vec3 position = texture2D(v_samplerPosition, uv).xyz;
vec3 velocity = texture2D(v_samplerVelocity, uv).xyz;
// velocity -= .5;
// velocity *= 3.;
// velocity = velocity * 2. - 1.;
vec3 pos = position + velocity * delta;
// This just adds a little touch more randomness to the motion.
// This is incredibly subtle but has the effect of making the particles
// look more "separate" in motion
vec3 hash = hash3(position_original.xy * position_original.zx * 20.);
// pos *= 1. + (hash - .5) * .0005;
// pos += (hash - .5) * .001;
// vec2 p = vec2(atan(pos.y, pos.x), length(pos.xy));
// p.x -= velocity.x * .001 + .0001;
// pos.x = cos(p.x) * p.y;
// pos.y = sin(p.x) * p.y;
// pos.z += .005;
if(length(pos) > 40.) {
pos = position_original;
}
gl_FragColor = vec4(pos, 1.0);
}
</script>
<div id="container" touch-action="none"></div>
CSS
body {
margin: 0;
padding: 0;
}
#container {
position: fixed;
touch-action: none;
}
JS
https://threejs.org/examples/js/GPUComputationRenderer.js
https://threejs.org/examples/js/controls/OrbitControls.js
https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/ccapture.js
скриптconst texturesize = 1024;
const particles = texturesize * texturesize;
let container;
let camera, scene, renderer, controls;
let cloud_obj;
let uniforms;
let gpuComputationRenderer, dataPos, dataVel, textureArraySize = texturesize*texturesize*4.;
let textureVelocity, texturePosition;
const particleVert = document.getElementById( 'vertexShaderParticle' ).textContent;
const particleFrag = document.getElementById( 'fragmentShaderParticle' ).textContent;
const velocityFrag = document.getElementById( 'fragmentShaderVelocity' ).textContent;
const positionFrag = document.getElementById( 'fragmentShaderPosition' ).textContent;
let loader=new THREE.TextureLoader();
let texture;
loader.setCrossOrigin("anonymous");
loader.load(
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/noise.png',
function do_something_with_texture(tex) {
texture = tex;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.minFilter = THREE.LinearFilter;
init();
animate();
}
);
function init() {
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera(65, 1, 0.001, Math.pow(2, 16));
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 50.;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff );
// create out particles
// ----------------------------
let vertices = new Float32Array(particles * 3).fill(0);
let references = new Float32Array(particles * 2);
for (let i = 0; i < references.length; i += 2) {
let index = i / 2;
references[i] = (index % texturesize) / texturesize;
references[i + 1] = Math.floor(index / texturesize) / texturesize;
}
let geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.addAttribute('reference', new THREE.BufferAttribute(references, 2));
// Create our particle material
// ----------------------------
uniforms = {
u_time: { type: "f", value: 1.0 },
u_resolution: { type: "v2", value: new THREE.Vector2() },
u_noise: { type: "t", value: texture },
u_mouse: { type: "v2", value: new THREE.Vector2() },
u_texturePosition: { value: null },
u_clicked: { type: 'b', value: true }
};
let particleMaterial = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: particleVert,
fragmentShader: particleFrag,
side: THREE.DoubleSide,
transparent: true
} );
particleMaterial.transparent = true;
particleMaterial.blending = THREE.MultiplyBlending;
particleMaterial.depthTest = false;
particleMaterial.extensions.derivatives = true;
// Create the particle cloud object
// ----------------------------
cloud_obj = new THREE.Points(geometry, particleMaterial);
scene.background = new THREE.Color( 0x111111 );
cloud_obj.material.blending = THREE.AdditiveBlending;
// scene.background = new THREE.Color( 0xFFFFFF );
// cloud_obj.material.blending = THREE.SubtractiveBlending;
// Create the renderer and controls and add them to the scene
// ----------------------------
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( 1 );
controls = new THREE.OrbitControls(camera, renderer.domElement);
window.controls = controls;
container.appendChild( renderer.domElement );
// Finally, add everything to stage
// ----------------------------
scene.add( cloud_obj );
// Add the computational renderer and populate it with data
// ----------------------------
gpuComputationRenderer = new GPUComputationRenderer(texturesize, texturesize, renderer);
dataPos_orig = gpuComputationRenderer.createTexture();
dataPos = gpuComputationRenderer.createTexture();
dataVel = gpuComputationRenderer.createTexture();
for (let i = 0; i < textureArraySize; i += 4) {
let radius = 2.;
let phi = Math.random() * Math.PI * 2.;
let costheta = Math.random() * 2. - 1.;
let u = Math.random();
let theta = Math.acos( costheta );
let r = radius * Math.cbrt( u );
let x = r * Math.sin( theta) * Math.cos( phi );
let y = r * Math.sin( theta) * Math.sin( phi );
let z = r * Math.cos( theta );
dataPos.image.data[i] = x;
dataPos.image.data[i + 1] = y;
dataPos.image.data[i + 2] = z;
dataPos.image.data[i + 3] = 1;
dataPos_orig.image.data[i] = x;
dataPos_orig.image.data[i + 1] = y;
dataPos_orig.image.data[i + 2] = z;
dataPos_orig.image.data[i + 3] = 1;
dataVel.image.data[i] = x * 3.;
dataVel.image.data[i + 1] = y * 3.;
dataVel.image.data[i + 2] = z * 3.;
dataVel.image.data[i + 3] = 1;
}
textureVelocity = gpuComputationRenderer.addVariable('v_samplerVelocity', velocityFrag, dataVel);
texturePosition = gpuComputationRenderer.addVariable('v_samplerPosition', positionFrag, dataPos);
texturePosition.material.uniforms.delta = { value: 0 };
texturePosition.material.uniforms.v_samplerPosition_orig = { type: "t", value: dataPos_orig };
textureVelocity.material.uniforms.u_time = { value: -1000 };
textureVelocity.material.uniforms.u_mousex = { value: 0 };
texturePosition.material.uniforms.u_time = { value: 0 };
gpuComputationRenderer
.setVariableDependencies(textureVelocity, [ textureVelocity, texturePosition ]);
gpuComputationRenderer
.setVariableDependencies(texturePosition, [ textureVelocity, texturePosition ]);
texturePosition.wrapS = THREE.RepeatWrapping;
texturePosition.wrapT = THREE.RepeatWrapping;
textureVelocity.wrapS = THREE.RepeatWrapping;
textureVelocity.wrapT = THREE.RepeatWrapping;
const gpuComputationRendererError = gpuComputationRenderer.init();
if (gpuComputationRendererError) {
console.error('ERROR', gpuComputationRendererError);
}
// Add event listeners for resize and mouse move
// ----------------------------
onWindowResize();
window.addEventListener( 'resize', onWindowResize, false );
document.addEventListener('pointermove', pointerMove);
// document.addEventListener('click', onclick);
// initialise the video renderer
}
function onWindowResize( event ) {
let w = window.innerWidth;
let h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize( w, h );
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
}
function pointerMove( event ) {
let ratio = window.innerHeight / window.innerWidth;
textureVelocity.material.uniforms.u_mousex.value = event.pageX;
uniforms.u_mouse.value.x = (event.pageX - window.innerWidth / 2) / window.innerWidth / ratio;
uniforms.u_mouse.value.y = (event.pageY - window.innerHeight / 2) / window.innerHeight * -1;
event.preventDefault();
}
function onclick() {
// return;
let newval = !uniforms.u_clicked.value;
uniforms.u_clicked.value = newval;
console.log(cloud_obj.material.blending);
if(newval === false) {
scene.background = new THREE.Color( 0xffffff );
cloud_obj.material.blending = THREE.MultiplyBlending;
} else {
scene.background = new THREE.Color( 0x000000 );
cloud_obj.material.blending = THREE.AdditiveBlending;
}
}
function animate(delta) {
requestAnimationFrame( animate );
render(delta);
}
let capturer = new CCapture( {
verbose: true,
framerate: 60,
// motionblurFrames: 4,
quality: 90,
format: 'webm',
workersPath: 'js/'
} );
let capturing = false;
isCapturing = function(val) {
if(val === false && window.capturing === true) {
capturer.stop();
capturer.save();
renderer.setPixelRatio( window.devicePixelRatio );
} else if(val === true && window.capturing === false) {
capturer.start();
controls.enabled = false;
renderer.setPixelRatio( 1 );
}
capturing = val;
}
toggleCapture = function() {
isCapturing(!capturing);
}
window.addEventListener('keyup', function(e) { if(e.keyCode == 68) toggleCapture(); });
let then = 0;
function render(delta) {
let now = Date.now() / 1000;
let _delta = now - then;
then = now;
gpuComputationRenderer.compute();
texturePosition.material.uniforms.delta.value = Math.min(_delta, 0.5);
textureVelocity.material.uniforms.u_time.value += .0005;
texturePosition.material.uniforms.u_time.value += _delta;
uniforms.u_time.value += _delta;
uniforms.u_texturePosition.value = gpuComputationRenderer.getCurrentRenderTarget(texturePosition).texture;
window.pos = gpuComputationRenderer.getCurrentRenderTarget(texturePosition);
renderer.render( scene, camera );
if(capturing) {
capturer.capture( renderer.domElement );
}
}