274 lines
8.4 KiB
HTML
274 lines
8.4 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang="en" >
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<title>CodePen - Particle Photos</title>
|
||
|
<link rel="stylesheet" href="./style.css">
|
||
|
|
||
|
</head>
|
||
|
<body>
|
||
|
<!-- partial:index.partial.html -->
|
||
|
<!-- this pen was built and maintained in https://github.com/callumacrae/sketchbook/blob/master/src/views/ParticlePhotoWebgl.vue -->
|
||
|
|
||
|
<p class="loading">loading…</p>
|
||
|
|
||
|
<div class="canvas-container" style="display: none">
|
||
|
<canvas
|
||
|
ref="canvas"
|
||
|
@click="status = status === 'playing' ? 'paused' : 'playing'"
|
||
|
@dragenter.prevent
|
||
|
@dragleave.prevent
|
||
|
@dragover.prevent
|
||
|
@drop.prevent="handleDrop"
|
||
|
></canvas>
|
||
|
<GlobalEvents target="window" @resize="setSize" />
|
||
|
|
||
|
<div class="help-container" v-show="showHelp || showIosWarning">
|
||
|
<div class="help" v-show="showIosWarning">
|
||
|
<h2 style="color: red">WARNING</h2>
|
||
|
<p>
|
||
|
Due to a bug in how iOS handles webgl instancing, this animation
|
||
|
performs incredibly poorly on iOS—to the point where it doesn't really
|
||
|
work at all. On my phone, it runs at 0.0075 fps and makes my phone
|
||
|
heat up to the temperature of the sun.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
I'd recommend trying this on desktop (it also just looks better), but
|
||
|
if you really want to see what happens on iOS, you can
|
||
|
<a
|
||
|
href
|
||
|
@click.prevent="
|
||
|
showIosWarning = false;
|
||
|
status = 'playing';
|
||
|
"
|
||
|
>
|
||
|
proceed anyway</a
|
||
|
>.
|
||
|
</p>
|
||
|
</div>
|
||
|
<div class="help" v-show="showHelp">
|
||
|
<h2>Instructions</h2>
|
||
|
|
||
|
<p>
|
||
|
This experiment visualises images as collections of animated coloured
|
||
|
particles.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
In addition to providing a few images (selectable on the configuration
|
||
|
in the top right of the screen), it also allows you to upload your own
|
||
|
by dragging and dropping them onto the canvas.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
Through a bit of experimentation, I've found the following makes the
|
||
|
best images:
|
||
|
</p>
|
||
|
|
||
|
<ul>
|
||
|
<li>
|
||
|
Lower resolution images or blurred images work better as it prevents
|
||
|
the particles from changing size and colour too quickly as they move
|
||
|
about. Most of the demo images are lower than 500px wide.
|
||
|
</li>
|
||
|
|
||
|
<li>
|
||
|
Images with lots of contrast between lighter and darker areas make
|
||
|
more effective graphics. Similarly, images where the details are on
|
||
|
the lighter parts of the images and the backgrounds are dark also
|
||
|
look better.
|
||
|
</li>
|
||
|
|
||
|
<li>
|
||
|
Some images, especially images with finer details, look better with
|
||
|
a high number of smaller points (adjust pointSizeMultiplier), while
|
||
|
others look better with a low number of larger points.
|
||
|
</li>
|
||
|
</ul>
|
||
|
|
||
|
<p>
|
||
|
To export high quality videos from this you'll need to
|
||
|
<a href="https://github.com/callumacrae/sketchbook">clone the repo</a>
|
||
|
and look for the <code>this.record</code> call in
|
||
|
ParticlePhotoWebgl.vue (or ask me for help on Twitter!)
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<p class="status" v-show="status === 'paused' && !showIosWarning" @click="status = 'playing'">
|
||
|
paused, click to resume
|
||
|
</p>
|
||
|
|
||
|
<a
|
||
|
v-if="!showIosWarning"
|
||
|
class="toggle-help-link"
|
||
|
href
|
||
|
@click.prevent="showHelp = !showHelp"
|
||
|
>
|
||
|
toggle help
|
||
|
</a>
|
||
|
</div>
|
||
|
|
||
|
<script type="nojs" id="vs">
|
||
|
precision mediump float;
|
||
|
|
||
|
//
|
||
|
// Description : Array and textureless GLSL 2D simplex noise function.
|
||
|
// Author : Ian McEwan, Ashima Arts.
|
||
|
// Maintainer : stegu
|
||
|
// 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
|
||
|
// https://github.com/stegu/webgl-noise
|
||
|
//
|
||
|
|
||
|
vec3 mod289(vec3 x) {
|
||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||
|
}
|
||
|
|
||
|
vec2 mod289(vec2 x) {
|
||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||
|
}
|
||
|
|
||
|
vec3 permute(vec3 x) {
|
||
|
return mod289(((x*34.0)+10.0)*x);
|
||
|
}
|
||
|
|
||
|
float noise2d(vec2 v)
|
||
|
{
|
||
|
const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
|
||
|
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
|
||
|
-0.577350269189626, // -1.0 + 2.0 * C.x
|
||
|
0.024390243902439); // 1.0 / 41.0
|
||
|
// First corner
|
||
|
vec2 i = floor(v + dot(v, C.yy) );
|
||
|
vec2 x0 = v - i + dot(i, C.xx);
|
||
|
|
||
|
// Other corners
|
||
|
vec2 i1;
|
||
|
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
|
||
|
//i1.y = 1.0 - i1.x;
|
||
|
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||
|
// x0 = x0 - 0.0 + 0.0 * C.xx ;
|
||
|
// x1 = x0 - i1 + 1.0 * C.xx ;
|
||
|
// x2 = x0 - 1.0 + 2.0 * C.xx ;
|
||
|
vec4 x12 = x0.xyxy + C.xxzz;
|
||
|
x12.xy -= i1;
|
||
|
|
||
|
// Permutations
|
||
|
i = mod289(i); // Avoid truncation effects in permutation
|
||
|
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
||
|
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
||
|
|
||
|
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
||
|
m = m*m ;
|
||
|
m = m*m ;
|
||
|
|
||
|
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
|
||
|
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
|
||
|
|
||
|
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||
|
vec3 h = abs(x) - 0.5;
|
||
|
vec3 ox = floor(x + 0.5);
|
||
|
vec3 a0 = x - ox;
|
||
|
|
||
|
// Normalise gradients implicitly by scaling m
|
||
|
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
|
||
|
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
||
|
|
||
|
// Compute final noise value at P
|
||
|
vec3 g;
|
||
|
g.x = a0.x * x0.x + h.x * x0.y;
|
||
|
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||
|
return 130.0 * dot(m, g);
|
||
|
}
|
||
|
|
||
|
attribute float a_x;
|
||
|
attribute float a_initial_offset;
|
||
|
attribute float a_speed;
|
||
|
|
||
|
uniform float u_time;
|
||
|
uniform sampler2D u_image_texture;
|
||
|
uniform float u_width;
|
||
|
uniform float u_x_in_noise_multiplier;
|
||
|
uniform float u_x_out_noise_multiplier;
|
||
|
uniform float u_y_in_noise_multiplier;
|
||
|
uniform float u_y_out_noise_multiplier;
|
||
|
uniform float u_point_size_multiplier;
|
||
|
|
||
|
varying vec2 v_position;
|
||
|
varying float v_initial_offset;
|
||
|
|
||
|
void main() {
|
||
|
vec2 position = vec2(2.0 * a_x - 1.0, 0.0);
|
||
|
|
||
|
float offset_y = mod(a_initial_offset + 1.0 + a_speed * u_time / 500000.0, 2.0) - 1.0;
|
||
|
|
||
|
position.x += noise2d(
|
||
|
vec2(a_x * u_x_in_noise_multiplier + 123.4, offset_y * 0.5 + u_time / 100000.0)
|
||
|
) * u_x_out_noise_multiplier;
|
||
|
position.y += offset_y + noise2d(
|
||
|
vec2(a_x * u_y_in_noise_multiplier, u_time / 10000.0 + 100.0)
|
||
|
) * u_y_out_noise_multiplier;
|
||
|
|
||
|
position.y *= 1.1;
|
||
|
|
||
|
v_position = position;
|
||
|
v_initial_offset = a_initial_offset;
|
||
|
|
||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||
|
gl_PointSize = u_width / 660.0 * u_point_size_multiplier;
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<script type="nojs" id="fs">
|
||
|
precision mediump float;
|
||
|
|
||
|
uniform sampler2D u_image_texture;
|
||
|
uniform float u_radius_val_exponent;
|
||
|
uniform float u_alpha_val_exponent;
|
||
|
uniform float u_alpha_val_multiplier;
|
||
|
uniform bool u_color;
|
||
|
uniform float u_twinkle_frequency;
|
||
|
uniform float u_twinkle_intensity;
|
||
|
uniform float u_twinkle_factor;
|
||
|
|
||
|
varying vec2 v_position;
|
||
|
varying float v_initial_offset;
|
||
|
|
||
|
void main() {
|
||
|
vec2 position = v_position;
|
||
|
position.y *= -1.0;
|
||
|
|
||
|
vec4 texture_color = texture2D(u_image_texture, (position + 1.0) / 2.0);
|
||
|
|
||
|
// http://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/
|
||
|
// The alpha multiplication stops random transparent white pixels from ruining everything
|
||
|
float intensity = (0.21 * texture_color.r + 0.71 * texture_color.g + 0.07 * texture_color.b) * texture_color.a;
|
||
|
|
||
|
float radius_val = pow(intensity, u_radius_val_exponent);
|
||
|
float alpha_val = pow(intensity, u_alpha_val_exponent) * u_alpha_val_multiplier;
|
||
|
|
||
|
float twinkle_y_normalised = abs(mod((position.y - v_initial_offset * 123.0) * 100.0, u_twinkle_frequency * 2.0) - u_twinkle_frequency);
|
||
|
float twinkle_value = 1.0 - u_twinkle_factor + u_twinkle_factor * smoothstep(
|
||
|
u_twinkle_frequency * (1.0 - 1.0 / u_twinkle_intensity / u_twinkle_frequency * 5.0),
|
||
|
u_twinkle_frequency,
|
||
|
twinkle_y_normalised
|
||
|
);
|
||
|
|
||
|
vec2 pc = (gl_PointCoord - 0.5) * 2.0;
|
||
|
float dist = sqrt(pc.x * pc.x + pc.y * pc.y);
|
||
|
float alpha = alpha_val * smoothstep(radius_val, radius_val - 0.1, dist) * twinkle_value;
|
||
|
|
||
|
gl_FragColor = vec4(u_color ? texture_color.rgb : vec3(1.0), alpha);
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
<!-- partial -->
|
||
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script><script type="module" src="./script.js"></script>
|
||
|
|
||
|
</body>
|
||
|
</html>
|