流动粒子(flow01001)
更多有趣示例 尽在知屋安砖社区
示例

HTML
<script id="vertexShader_particle" type="x-shader/x-vertex">
attribute vec3 a_position;
attribute vec3 a_particle;
attribute vec2 a_reference;
uniform float u_time;
uniform mat4 u_m_model;
uniform mat4 u_m_view;
uniform mat4 u_m_MVP;
uniform mat4 u_m_proj;
uniform sampler2D b_position;
uniform sampler2D b_velocity;
varying vec3 v_colour;
varying float v_fogDepth;
varying float v_opacity;
float random(vec2 st) {
return fract(sin(dot(st,
vec2(12.9898,78.233)))*
43758.5453123);
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
float hash21(vec2 p) {
p = fract(p * vec2(233.34, 851.74));
p += dot(p, p + 23.45);
return fract(p.x * p.y);
}
mat3 fromQuat(vec4 q) {
float x = q.x;
float y = q.y;
float z = q.z;
float w = q.w;
float x2 = q.x*2.;
float y2 = q.y*2.;
float z2 = q.z*2.;
float xx = x * x2;
float yx = y * x2;
float yy = y * y2;
float zx = z * x2;
float zy = z * y2;
float zz = z * z2;
float wx = w * x2;
float wy = w * y2;
float wz = w * z2;
return mat3(
1. - yy -zz, yx -wz, zx + wy,
yx + wz, 1. - xx - zz, zy - wx,
zx - wy, zy + wx, 1. - xx - yy
);
}
/**
* Generates a look-at matrix with the given eye position, focal point, and up axis.
* If you want a matrix that actually makes an object look at another object, you should use targetTo instead.
*
* @param {mat4} out mat4 frustum matrix will be written into
* @param {vec3} eye Position of the viewer
* @param {vec3} center Point the viewer is looking at
* @param {vec3} up vec3 pointing up
* @returns {mat4} out
*/
mat4 lookAt(vec3 e, vec3 c, vec3 u) {
// if (Math.abs(e.x - c.x) < EPSILON &&
// Math.abs(e.y - c.y) < EPSILON &&
// Math.abs(e.z - c.z) < EPSILON) {
// return new Mat4();
// }
vec3 off = normalize(e - c);
vec3 or = vec3(
u.y * off.z - u.z * off.y,
u.z * off.x - u.x * off.z,
u.x * off.y - u.y * off.x
);
or = normalize(or);
vec3 tn = vec3(
off.y * or.z - off.z * or.y,
off.z * or.x - off.x * or.z,
off.x * or.y - off.y * or.x
);
tn = normalize(tn);
return mat4(
or.x,
tn.x,
off.x,
0,
or.y,
tn.y,
off.y,
0,
or.z,
tn.z,
off.z,
0,
-(or.x * e.x + or.y * e.y + or.z * e.z),
-(tn.x * e.x + tn.y * e.y + tn.z * e.z),
-(off.x * e.x + off.y * e.y + off.z * e.z),
1
);
}
vec3 palette( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) {
return a + b*cos( 6.28318*(c*t+d) );
}
void main() {
// vec4 pos = vec4(a_particle, 1.);
// vec4 mvPos = u_m_view * u_m_model * pos;
// gl_Position = u_m_proj * mvPos;
// float isq = ( 1. / -mvPos.z );
// gl_PointSize = (100.) * isq;
vec3 position = texture2D(b_position, a_reference).xyz;
vec3 velocity = texture2D(b_velocity, a_reference).xyz;
// vec4 quat = vec4(normalize(velocity), 0.);
// mat3 mat = fromQuat(quat);
// vec3 particle = a_particle * vec3(1, 1.+length(velocity*velocity*.05), 1) * .1 * mat;
float vl = min(length(velocity*velocity)*.01, 5.);
vec3 p = a_particle * vec3(1.-vl*.2,1,1.+vl);
float vl1 = smoothstep(0., 20., length(velocity));
p *= (.3 + vl1);
mat4 look = lookAt(
(vec3(0,0,0)),
normalize(velocity),
(vec3(0,1,0))
);
vec3 particle = (vec4(p * .2, 1) * look).xyz;
// vec3 particle = (vec4(a_particle.yzx*vec3(1,1,1.+length(velocity*velocity)*.01), 1) * .2 * look).xyz;
position += particle;
vec4 pos = vec4(position, 1.);
float l = length(pos);
vec4 mvPos = u_m_view * u_m_model * pos;
v_fogDepth = mvPos.z;
float isq = ( 1. / -mvPos.z );
float b = smoothstep(0., 80., l);
float s = clamp(b*6., 0.1, 1.);
v_opacity = b;
gl_Position = u_m_proj * mvPos;
float hash = hash21(a_reference);
v_colour = palette(
hash*.5+.3,
vec3(0.5,0.5,0.5),
vec3(0.5,0.5,0.5),
vec3(1.0,1.0,1.0),
vec3(0.5,0.3,0.2)
);
// v_colour = hsv2rgb(vec3(.6 + hash * .3 + vl1 * .1, 1., hash * .5 + .3));
// if(length(a_reference) == 0.) v_colour = vec3(1,0,0);
}
</script>
<script id="vertexShader_buffer" type="x-shader/x-vertex">attribute vec4 a_position;
uniform mat4 u_modelViewMatrix;
uniform mat4 u_projectionMatrix;
void main() {
gl_Position = a_position;
}
</script>
<script id="fragmentShader_velocity" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D s_noise;
uniform int u_frame;
uniform float u_nsize;
uniform float u_seed;
uniform sampler2D b_velocity;
uniform sampler2D b_position;
#define PI 3.141592653589793
#define HPI 1.5707963267948966
#define TAU 6.283185307179586
#define G 0.67408
mat4 rotationMatrix(vec3 axis, float angle)
{
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0);
}
vec3 hash33(vec3 p) {
return fract(vec3(
sin(p.x) * 43543.454354,
sin(p.y) * 7531.154354,
sin(p.z) * 10053.75315
));
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec3 position = texture2D(b_position, uv).xyz;
vec3 velocity = texture2D(b_velocity, uv).xyz;
vec3 acceleration = vec3(position);
vec3 f = position;
float tm = u_time;
float l = length(position);
position = ( vec4(position, 1.) * rotationMatrix(vec3(sin(tm * 25.), cos(tm * 10.), sin(tm) * cos(tm * 5.)), .5 + 10. / l) ).xyz;
vec3 spherical = vec3(1./max(l, .1), atan(position.y, position.x), acos(position.z / l));
float a = sin(length(spherical.yz) * 5. + tm) * 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;
f = normalize(f - acceleration) * -1.;
f *= smoothstep(10., 40., l) * 2.;
vec3 vel = velocity * .99 + (acceleration + f) * .5;
gl_FragColor = vec4(vel, 1.0);
}
</script>
<script id="fragmentShader_position" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform float u_delta;
uniform sampler2D s_noise;
uniform bool u_nreset;
uniform sampler2D b_prime;
uniform sampler2D b_velocity;
uniform sampler2D b_position;
uniform sampler2D b_origin;
vec2 getScreenSpace() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
return uv;
}
vec3 hash33(vec3 p) {
return fract(vec3(
sin(p.x) * 43543.454354,
sin(p.y) * 7531.154354,
sin(p.z) * 10053.75315
));
}
void main() {
vec2 uv = getScreenSpace();
vec2 s = gl_FragCoord.xy / u_resolution.xy;
vec3 position = texture2D(b_position, s).xyz;
vec3 velocity = texture2D(b_velocity, s).xyz;
vec3 pos = position + velocity * u_delta * .5;
if(length(pos) > 100.) {
pos = pos / length(pos) * 2.;
}
gl_FragColor = vec4(pos, 1.0);
}
</script>
<script id="fragmentShader_particle" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D s_noise;
uniform bool u_transition;
uniform float u_transition_val;
uniform sampler2D b_prime;
uniform sampler2D b_position;
varying vec3 v_colour;
varying float v_fogDepth;
varying float v_opacity;
vec2 getScreenSpace() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
return uv;
}
void main() {
float fade = smoothstep(1., 0., u_transition_val);
gl_FragColor = vec4(mix(vec3(1), v_colour, fade), 1.);
// vec2 uv = gl_PointCoord.xy - .5;
// vec2 s = gl_FragCoord.xy / u_resolution.xy;
// float l = length(uv);
// float c = smoothstep(.5, 0., l);
// float fog = smoothstep(-200., -1., v_fogDepth);
// float opacity = c*fog;
// if(c < .1) discard;
// float fade = smoothstep(1., 0., u_transition_val);
// gl_FragColor = vec4(
// mix(
// vec4(1.),
// mix(
// vec4(1),
// vec4(v_colour, opacity),
// c),
// fade
// )
// );
}
</script>
<script id="fragmentShader_blur" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D s_noise;
uniform sampler2D b_prime;
uniform sampler2D b_blur;
varying vec3 v_colour;
vec2 getScreenSpace() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
return uv;
}
void main() {
vec2 uv = getScreenSpace();
vec2 s = gl_FragCoord.xy / u_resolution.xy;
vec4 n1 = texture2D(b_blur, s);
vec4 n = clamp(texture2D(b_prime, s), 0., 1.);
vec4 c = n1*.5 + n*.5;
gl_FragColor = clamp(c, 0., 1.);
}
</script>
<script id="fragmentShader_bloom" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D s_noise;
uniform int u_bloomstep;
uniform sampler2D b_prime;
uniform sampler2D b_blur;
uniform sampler2D b_bloom;
varying vec3 v_colour;
vec2 getScreenSpace() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
return uv;
}
vec4 tex(sampler2D tex, vec2 co) {
return clamp(texture2D(tex, co), 0., 1.);
}
void main() {
vec2 uv = getScreenSpace();
vec2 s = gl_FragCoord.xy / u_resolution.xy;
vec2 p = 1./u_resolution.xy;
// vec4 n1 = texture2D(b_blur, s);
// vec4 n = texture2D(b_prime, s);
vec4 n1;
vec4 c;
vec4 n = texture2D(b_prime, s);
vec4 n2 = n;
if(u_bloomstep == 0) {
n1 = tex(b_blur, s);
n1 += tex(b_blur, s + vec2(0, p.y));
n1 += tex(b_blur, s + vec2(0, p.y*2.));
n1 += tex(b_blur, s + vec2(0, p.y*3.));
n1 += tex(b_blur, s + vec2(0, p.y*-1.));
n1 += tex(b_blur, s + vec2(0, p.y*-2.));
n1 += tex(b_blur, s + vec2(0, p.y*-3.));
n1/=7.;
c = n1;
} else if(u_bloomstep == 1) {
n1 = tex(b_bloom, s);
n1 += tex(b_bloom, s + vec2(p.x, 0.));
n1 += tex(b_bloom, s + vec2(p.x*2., 0));
n1 += tex(b_bloom, s + vec2(p.x*3., 0));
n1 += tex(b_bloom, s + vec2(p.x*-1., 0));
n1 += tex(b_bloom, s + vec2(p.x*-2., 0));
n1 += tex(b_bloom, s + vec2(p.x*-3., 0));
n1/=7.;
c = pow(n1, vec4(2.));
}
gl_FragColor = clamp(n1, 0., 1.);
}
</script>
<script id="fragmentShader_buffer" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision highp float;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D s_noise;
uniform bool u_transition;
uniform float u_transition_val;
uniform sampler2D b_prime;
uniform sampler2D b_blur;
uniform sampler2D b_bloom;
varying vec3 v_colour;
vec2 getScreenSpace() {
vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.y, u_resolution.x);
return uv;
}
void main() {
vec2 uv = getScreenSpace();
vec2 s = gl_FragCoord.xy / u_resolution.xy;
vec2 p = .5/u_resolution.xy;
// vec4 bloom = texture2D(b_bloom, s);
vec4 n = texture2D(b_blur, s);
float fade = smoothstep(1., 0., u_transition_val);
gl_FragColor = vec4(
mix(
vec3(1),
n.rgb, fade) * (1. + (1.-fade)), 1.);
}
</script>
<div class="controls">
<div class="playpause checked">
<label>
<input checked="checked" type="checkbox" value="None" id="playpause" name="check" />
</label>
</div>
</div>
CSS
body {
background: #66f;
margin: 0;
overflow: hidden;
}
canvas {
height: 100vh;
width: 100vw;
touch-action: none;
}
.osc {
left: 0px;
position: fixed;
top: 0px;
}
.button {
position: fixed;
z-index: 10;
right: 0;
bottom: 0;
}
.controls {
position: fixed;
z-index: 10;
left: 0;
bottom: 0;
}
.playpause {
background: #AAB;
padding: 10px;
}
.playpause label {
display: block;
box-sizing: border-box;
width: 0;
height: 20px;
cursor: pointer;
border-color: transparent transparent transparent #202020;
transition: 100ms all ease;
will-change: border-width;
border-style: double;
border-width: 0px 0 0px 20px;
}
.playpause input[type='checkbox'] {
visibility: hidden;
}
.playpause.checked label {
border-style: double;
border-width: 0px 0 0px 20px;
}
.playpause label {
border-style: solid;
border-width: 10px 0 10px 20px;
}
/* } */
JS
import { Vec2, Vec3, Mat2, Mat3, Mat4, Quat } from 'https://cdn.skypack.dev/wtc-math';
import gifJs from 'https://cdn.skypack.dev/gif.js';
console.clear();
// Determine whether a number is a power of 2
function powerOf2(v) {
return v && !(v & (v - 1));
}
// Return the next greatest power of 2
function nextPow2( v ) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
// Update a provided image to the nearest power of 2 in size.
const pow2Image = (c) => {
const newWidth = powerOf2(c.width) ? c.width : nextPow2(c.width);
const newHeight = powerOf2(c.height) ? c.height : nextPow2(c.height);
const _c = document.createElement('canvas');
const ctx = _c.getContext('2d');
_c.width = newWidth;
_c.height = newHeight;
ctx.drawImage(c, 0, 0, newWidth, newHeight);
return _c;
}
const asyncImageLoad = function(img, src) {
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
})
}
const glEnumToString = (function() {
const haveEnumsForType = {};
const enums = {};
function addEnums(gl) {
const type = gl.constructor.name;
if (!haveEnumsForType[type]) {
for (const key in gl) {
if (typeof gl[key] === 'number') {
const existing = enums[gl[key]];
enums[gl[key]] = existing ? `${existing} | ${key}` : key;
}
}
haveEnumsForType[type] = true;
}
}
return function glEnumToString(gl, value) {
addEnums(gl);
return enums[value] || (typeof value === 'number' ? `0x${value.toString(16)}` : value);
};
}());
const addExtensions = (ctx) => {
// Set up the extensions
ctx.getExtension('OES_standard_derivatives');
ctx.getExtension('EXT_shader_texture_lod');
ctx.getExtension('OES_texture_float');
ctx.getExtension('WEBGL_color_buffer_float');
ctx.getExtension('OES_texture_float_linear');
ctx.getExtension('EXT_color_buffer_float');
}
function createContext(c, opt_attribs, params) {
const ctx = c.getContext("webgl", params) || this._el.getContext("experimental-webgl", params);
addExtensions(ctx);
return ctx;
}
const quatToMat4 = (q) => {
if(q.array) q = q.array; // This just transforms a provided vector into to an array.
if(q instanceof Array && q.length >= 4) {
const [x, y, z, w] = q;
const [x2, y2, z2] = q.map(x => x * 2.);
const xx = x * x2,
yx = y * x2,
yy = y * y2,
zx = z * x2,
zy = z * y2,
zz = z * z2,
wx = w * x2,
wy = w * y2,
wz = w * z2;
return new Mat4(
1 - yy -zz, yx -wz, zx + wy, 0,
yx + wz, 1 - xx - zz, zy - wx, 0,
zx - wy, zy + wx, 1 - xx - yy, 0,
0, 0, 0, 1
);
}
}
class Renderer {
static #defaultOptions = {
width: 512,
height: 512,
pxRatio: Math.min(window.devicePixelRatio, 2),
clearing: true,
depthTesting: true,
premultipliedAlpha: true
}
static BLENDING_DEBUG = -1;
static BLENDING_NORMAL = 1;
static BLENDING_ADDITIVE = 2;
static BLENDING_SUBTRACTIVE = 4;
static BLENDING_MULTIPLY = 8;
static BLENDING_OFF = 16;
isWebgl2 = false;
#blending;
#blendingEnabled = false;
#buffers = [];
constructor(canvas, options) {
options = Object.assign({}, Renderer.#defaultOptions, options);
this.width = options.width;
this.height = options.height;
this.pxRatio = options.pxRatio;
this.clearing = options.clearing;
this.depthTesting = options.depthTesting;
this.canvas = canvas || document.createElement('canvas');
this.canvas.width = this.width * this.pxRatio;
this.canvas.height = this.height * this.pxRatio;
this.premultipliedAlpha = options.premultipliedAlpha;
this.ctx = this.canvas.getContext("webgl", options) || this.canvas.getContext("experimental-webgl", options);
this.ctx.viewportWidth = this.canvas.width;
this.ctx.viewportHeight = this.canvas.height;
this.uniformResolution = new Uniform(this.ctx, 'resolution', Uniform.TYPE_V2, [this.canvas.width, this.canvas.height]);
this.addExtensions();
}
resize(w, h, ratio) {
this.width = w;
this.height = h;
this.pxRatio = ratio || this.pxRatio;
this.canvas.width = this.width * this.pxRatio;
this.canvas.height = this.height * this.pxRatio;
this.ctx.viewportWidth = this.canvas.width;
this.ctx.viewportHeight = this.canvas.height;
this.uniformResolution = new Uniform(this.ctx, 'resolution', Uniform.TYPE_V2, [this.canvas.width, this.canvas.height]);
}
setViewport(dimensions) {
let w = this.width*this.pxRatio;
let h = this.height*this.pxRatio;
if(dimensions) {
w = dimensions[0];
h = dimensions[1];
}
this.ctx.viewport(0, 0, w, h);
this.uniformResolution = new Uniform(this.ctx, 'resolution', Uniform.TYPE_V2, [w, h]);
}
addExtensions() {
this.ctx.getExtension('OES_standard_derivatives');
this.ctx.getExtension('EXT_shader_texture_lod');
this.ctx.getExtension('OES_texture_float');
this.ctx.getExtension('WEBGL_color_buffer_float');
this.ctx.getExtension('OES_texture_float_linear');
this.ctx.getExtension('EXT_color_buffer_float');
}
linkBuffer(buffer) {
let hasBuffer = false;
this.#buffers.forEach((b) => {
if(buffer === b) hasBuffer = true;
});
if(!hasBuffer) {
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, buffer.buffer);
this.ctx.bufferData(
this.ctx.ARRAY_BUFFER,
buffer.data,
buffer.drawType);
}
buffer.link(this.currentProgram.program);
}
setupProgram(program, buffers, attributes, uniforms) {
this.currentProgram = program;
this.ctx.useProgram(program.program);
this.premultiplied = program.premultiplied;
this.depthTesting = program.depthTesting;
if(program.blending === Program.BLENDING_NORMAL && program.transparent === false ) {
this.blending = Program.BLENDING_OFF;
} else {
this.blending = program.blending;
}
this.clearColour = program.clearColour;
const a = this.clearColour[3];
// console.log('prem', this.premultipliedAlpha)
if(this.premultipliedAlpha) this.clearColour = this.clearColour.map((c, i) => c * a );
this.ctx.clearColor(...this.clearColour);
// TODO: Unlink unused buffers during this setup phase as well.
buffers.forEach(buffer => {
this.linkBuffer(buffer);
});
// this.ctx.enable(ctx.DEPTH_TEST);
if(this.depthTesting) this.ctx.enable(ctx.DEPTH_TEST);
else this.ctx.disable(ctx.DEPTH_TEST);
uniforms.forEach(uniform => {
uniform.bind(program.program);
});
this.uniformResolution.bind(program.program);
}
render(points, buffer) {
this.ctx.bindFramebuffer(this.ctx.FRAMEBUFFER, buffer?.fb || null);
if(this.clearing) {
this.ctx.clear( this.ctx.COLOR_BUFFER_BIT );
if(this.depthTesting) this.ctx.clear( this.ctx.DEPTH_BUFFER_BIT );
}
switch(this.currentProgram.renderType) {
case Program.RENDER_TRIANGLES:
this.ctx.drawArrays(this.ctx.TRIANGLES, 0, points);
break;
case Program.RENDER_STRIP:
this.ctx.drawArrays(this.ctx.TRIANGLE_STRIP, 0, points);
break;
case Program.RENDER_LINES:
this.ctx.drawArrays(this.ctx.LINE_STRIP, 0, points);
break;
case Program.RENDER_LINELOOP:
this.ctx.drawArrays(this.ctx.LINE_LOOP, 0, points);
break;
case Program.RENDER_POINTS:
this.ctx.drawArrays(this.ctx.POINTS, 0, points);
break;
}
}
/* SETTERS AND GETTERS */
get blending() {
return this.#blending || Program.BLENDING_NORMAL;
}
set blending(blending) {
if(blending === Renderer.BLENDING_DEBUG) {
if(!this.breakLog) {
console.log(blending, Renderer.BLENDING_OFF, this.premultiplied)
this.breakLog = true;
}
this.#blending = blending;
this.ctx.enable(this.ctx.BLEND);
this.ctx.blendFuncSeparate( this.ctx.ONE, this.ctx.ONE_MINUS_SRC_ALPHA, this.ctx.ONE, this.ctx.ONE_MINUS_SRC_ALPHA );
return;
}
this.#blending = blending;
if(blending === Renderer.BLENDING_OFF) {
this.ctx.disable(this.ctx.BLEND);
this.#blendingEnabled = false;
return;
}
if ( this.#blendingEnabled === false ) {
this.ctx.enable(this.ctx.BLEND);
// this.ctx.alphaFunc(this.ctx.GL_GREATER, 0.5);
// this.ctx.enable(this.ctx.GL_ALPHA_TEST);
this.#blendingEnabled = true;
}
if( this.premultiplied ) {
switch (this.blending) {
case Renderer.BLENDING_NORMAL:
this.ctx.blendFuncSeparate( this.ctx.ONE, this.ctx.ONE_MINUS_SRC_ALPHA, this.ctx.ONE, this.ctx.ONE_MINUS_SRC_ALPHA );
break;
case Renderer.BLENDING_ADDITIVE:
this.ctx.blendFunc( this.ctx.ONE, this.ctx.ONE );
break;
case Renderer.BLENDING_SUBTRACTIVE:
this.ctx.blendFuncSeparate( this.ctx.ZERO, this.ctx.ZERO, this.ctx.ONE_MINUS_SRC_COLOR, this.ctx.ONE_MINUS_SRC_ALPHA );
break;
case Renderer.BLENDING_MULTIPLY:
this.ctx.blendFuncSeparate( this.ctx.ZERO, this.ctx.SRC_COLOR, this.ctx.ZERO, this.ctx.SRC_ALPHA );
break;
}
} else {
switch (this.blending) {
case Renderer.BLENDING_NORMAL:
this.ctx.blendFuncSeparate( this.ctx.SRC_ALPHA, this.ctx.ONE_MINUS_SRC_ALPHA, this.ctx.ONE, this.ctx.ONE_MINUS_SRC_ALPHA );
break;
case Renderer.BLENDING_ADDITIVE:
this.ctx.blendFunc( this.ctx.SRC_ALPHA, this.ctx.ONE );
break;
case Renderer.BLENDING_SUBTRACTIVE:
this.ctx.blendFunc( this.ctx.ZERO, this.ctx.ONE_MINUS_SRC_COLOR );
break;
case Renderer.BLENDING_MULTIPLY:
this.ctx.blendFunc( this.ctx.ZERO, this.ctx.SRC_COLOR );
break;
}
}
}
}
class Buffer {
static #defaultAttribute = {
numComponents: 2,
offset: 0,
stride: 0
};
static #defaults = {
attributes: [{
name: 'position'
}
],
normalized: false,
drawType: window.WebGLRenderingContext.STATIC_DRAW,
type: window.WebGLRenderingContext.FLOAT
}
constructor(ctx, data, options) {
this.ctx = ctx;
this.name = name;
options = Object.assign({}, Buffer.#defaults, options);
this.attributes = options.attributes.map(a => Object.assign({}, Buffer.#defaultAttribute, a));
this.normalized = options.normalized;
this.drawType = options.drawType;
this.type = options.type;
if(data instanceof Array) data = new Float32Array(data);
this.data = data;
this.buffer = ctx.createBuffer();
}
link(program, hasBuffer = false) {
let location = this.ctx.getAttribLocation(program, `a_${this.name}`);
this.attributes.forEach(attribute => {
const location = this.ctx.getAttribLocation(program, `a_${attribute.name}`);
this.ctx.vertexAttribPointer(location, attribute.numComponents, this.type, this.normalized, attribute.stride, attribute.offset);
this.ctx.enableVertexAttribArray(location);
});
}
get length() {
return this.data.length;
}
}
class Program {
static RENDER_TRIANGLES = 0;
static RENDER_STRIP = 1;
static RENDER_LINES = 2;
static RENDER_LINELOOP = 4;
static RENDER_POINTS = 8;
static #defaultOptions = {
renderType: Program.RENDER_TRIANGLES,
clearColour: [1.0, 1.0, 1.0, 1.0],
blending: Renderer.BLENDING_OFF,
premultiplied: true,
transparent: false,
depthTesting: true
}
#vShader
#fShader
#p
#renderType
constructor(ctx, vertexShaderSource, fragmentShaderSource, options = {}) {
options = Object.assign({}, Program.#defaultOptions, options);
this.ctx = ctx;
this.renderType = options.renderType;
this.clearColour = options.clearColour;
this.blending = options.blending;
this.premultiplied = options.premultiplied;
this.transparent = options.transparent;
this.depthTesting = options.depthTesting;
// Create the shaders
this.vShader = Program.createShaderOfType(this.ctx, this.ctx.VERTEX_SHADER, vertexShaderSource);
this.fShader = Program.createShaderOfType(this.ctx, this.ctx.FRAGMENT_SHADER, fragmentShaderSource);
// Create the program and link the shaders
this.#p = this.ctx.createProgram();
this.ctx.attachShader(this.#p, this.vShader);
this.ctx.attachShader(this.#p, this.fShader);
this.ctx.linkProgram(this.#p);
// Check the result of linking
var linked = this.ctx.getProgramParameter(this.#p, this.ctx.LINK_STATUS);
if (!linked) {
var error = this.ctx.getProgramInfoLog(this.#p);
console.log('Failed to link program: ' + error);
this.ctx.deleteProgram(this.#p);
this.ctx.deleteShader(this.fShader);
this.ctx.deleteShader(this.vShader);
}
}
get program() {
return this.#p;
}
/* SETTERS AND GETTERS */
set renderType(value) {
if([
Program.RENDER_TRIANGLES,
Program.RENDER_STRIP,
Program.RENDER_LINES,
Program.RENDER_LINELOOP,
Program.RENDER_POINTS
].indexOf(value) > -1) this.#renderType = value;
}
get renderType() {
return this.#renderType;
}
/**
* Static Methods
*/
/**
* Create a shader of a given type given a context, type and source.
*
* @static
* @param {WebGLContext} ctx The context under which to create the shader
* @param {WebGLShaderType} type The shader type, vertex or fragment
* @param {string} source The shader source.
* @return {WebGLShader} The created shader
*/
static createShaderOfType(ctx, type, source) {
const shader = ctx.createShader(type);
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
// Check the compile status
const compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
if (!compiled) {
// Something went wrong during compilation; get the error
const lastError = ctx.getShaderInfoLog(shader);
console.error(`${Program.addLineNumbersWithError(source, lastError)}\nError compiling ${glEnumToString(ctx, type)}: ${lastError}`);
ctx.deleteShader(shader);
return null;
}
return shader;
}
static addLineNumbersWithError(src, log = '') {
console.log(src)
const errorRE = /ERROR:\s*\d+:(\d+)/gi;
// Note: Error message formats are not defined by any spec so this may or may not work.
const matches = [...log.matchAll(errorRE)];
const lineNoToErrorMap = new Map(matches.map((m, ndx) => {
const lineNo = parseInt(m[1]);
const next = matches[ndx + 1];
const end = next ? next.index : log.length;
const msg = log.substring(m.index, end);
return [lineNo - 1, msg];
}));
return src.split('\n').map((line, lineNo) => {
const err = lineNoToErrorMap.get(lineNo);
return `${lineNo + 1}: ${line}${err ? `\n\n^^^ ${err}` : ''}`;
}).join('\n');
}
}
class Uniform {
static TYPE_INT = 0
static TYPE_FLOAT = 1
static TYPE_V2 = 2
static TYPE_V3 = 3
static TYPE_V4 = 4
static TYPE_BOOL = 5
static TYPE_M2 = 6
static TYPE_M3 = 7
static TYPE_M4 = 8
#prefix = 'u'
constructor(ctx, name, type, value) {
this.ctx = ctx;
this.name = name;
this.type = type;
this.value = value;
}
prebind() {
}
bind(program) {
this.prebind(program);
const location = this.ctx.getUniformLocation(program, `${this.#prefix}_${this.name}`);
switch(this.type) {
case Uniform.TYPE_INT :
if(!isNaN(this.value)) this.ctx.uniform1i( location, this.value );
break;
case Uniform.TYPE_FLOAT :
if(!isNaN(this.value)) this.ctx.uniform1f( location, this.value);
break;
case Uniform.TYPE_V2 :
if(this.value instanceof Array && this.value.length === 2.) this.ctx.uniform2fv( location, this.value);
break;
case Uniform.TYPE_V3 :
if(this.value instanceof Array && this.value.length === 3.) this.ctx.uniform3fv( location, this.value);
break;
case Uniform.TYPE_V4 :
if(this.value instanceof Array && this.value.length === 4.) this.ctx.uniform4fv( location, this.value);
break;
case Uniform.TYPE_BOOL :
if(!isNaN(this.value)) this.ctx.uniform1i( location, this.value);
break;
case Uniform.TYPE_M2 :
if(this.value instanceof Array && this.value.length === 4.) this.ctx.uniformMatrix2fv( location, false, this.value);
case Uniform.TYPE_M3 :
if(this.value instanceof Array && this.value.length === 9.) this.ctx.uniformMatrix3fv( location, false, this.value);
case Uniform.TYPE_M4 :
if(this.value instanceof Array && this.value.length === 16.) this.ctx.uniformMatrix4fv( location, false, this.value);
break;
}
}
}
class Texture extends Uniform {
#prefix = 's'
static #defaultOptions = {
textureType: 0,
minFilter: window.WebGLRenderingContext.LINEAR,
magFilter: window.WebGLRenderingContext.LINEAR,
makePowerOf2: false,
generateMipMap: false
}
static masteri = 0
static IMAGETYPE_REGULAR = 0
static IMAGETYPE_TILE = 1
static IMAGETYPE_MIRROR = 2
constructor(ctx, name, options) {
super(ctx, name, 0, null);
options = Object.assign({}, Texture.#defaultOptions, options);
this.textureType = options.textureType;
this.minFilter = options.minFilter;
this.magFilter = options.magFilter;
this.makePowerOf2 = options.makePowerOf2;
this.generateMipMap = options.generateMipMap;
this.url = options.url;
this.data = options.data;
this.value = Texture.masteri++;
}
async preload() {
const store = {};
const img = new Image();
img.crossOrigin = "anonymous";
await asyncImageLoad(img, this.url);
if(this.makePowerOf2) this.image = pow2Image(img);
else this.image = img;
// this.loadTexture(gl, n, store);
return this;
}
prebind(program) {
if(!this.image) return;
const texture = this.ctx.createTexture(); // Create the texture object
this.ctx.pixelStorei(this.ctx.UNPACK_FLIP_Y_WEBGL, true);
this.ctx.bindTexture(this.ctx.TEXTURE_2D, texture);
this.ctx.activeTexture(this.ctx.TEXTURE0 + this.value);
// Set the parameters based on the passed type
// In WebGL images are wrapped by default, so we don't need to check for that
if(this.textureType === Texture.IMAGETYPE_MIRROR) {
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.MIRRORED_REPEAT);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.MIRRORED_REPEAT);
} else if(this.textureType === Texture.IMAGETYPE_REGULAR) {
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
}
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.minFilter);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MAG_FILTER, this.magFilter);
// Upload the image into the texture.
if(this.data) {
this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, this.ctx.RGBA, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, this.data);
} else {
this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, this.ctx.RGBA, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, this.image);
}
if(this.generateMipMap) this.ctx.generateMipmap(this.ctx.TEXTURE_2D);
}
}
class FrameBuffer {
static #defaultOptions = {
width: 512,
height: 512,
pxRatio: Math.min(window.devicePixelRatio, 2),
tiling: Texture.IMAGETYPE_REGULAR,
texdepth: FrameBuffer.TEXTYPE_HALF_FLOAT_OES,
data: null,
depthTesting: false
}
static TEXTYPE_FLOAT = 0
static TEXTYPE_UNSIGNED_BYTE = 1
static TEXTYPE_HALF_FLOAT_OES = 2
#fb1
#fb2
#activeFB
#name
#width
#height
#pxRatio
#tiling = Texture.IMAGETYPE_REGULAR
#texdepth = FrameBuffer.TEXTYPE_HALF_FLOAT_OES
#data;
constructor(renderer, name, options) {
options = Object.assign({}, FrameBuffer.#defaultOptions, options);
this.width = options.width;
this.height = options.height;
this.pxRatio = options.pxRatio;
this.tiling = options.tiling;
this.texdepth = options.texdepth;
this.depthTesting = options.depthTesting;
this.#name = name;
this.value = Texture.masteri++;
this.ctx = renderer.ctx;
this.renderer = renderer;
this.data = options.data;
this.#fb1 = this.createFrameBuffer();
this.#fb2 = this.createFrameBuffer();
this.#activeFB = this.#fb1;
}
resize(width, height) {
this.width = width;
this.height = height;
this.#fb1 = this.createFrameBuffer();
this.#fb2 = this.createFrameBuffer();
this.#activeFB = this.#fb1;
}
createFrameBuffer() {
const targetTexture = this.ctx.createTexture();
this.ctx.bindTexture(this.ctx.TEXTURE_2D, targetTexture);
{
// define size and format of level 0
const level = 0;
let internalFormat = this.ctx.RGBA;
const border = 0;
let format = this.ctx.RGBA;
let t;
if(this.texdepth === FrameBuffer.TEXTYPE_FLOAT) {
const e = this.ctx.getExtension('OES_texture_float');
t = this.ctx.FLOAT;
// internalFormat = this.ctx.FLOAT;
// format = this.ctx.FLOAT;
} else if(this.texdepth & FrameBuffer.TEXTYPE_HALF_FLOAT_OES) {
// t = gl.renderer.isWebgl2 ? e.HALF_FLOAT : e.HALF_FLOAT_OES;
// gl.renderer.extensions['OES_texture_half_float'] ? gl.renderer.extensions['OES_texture_half_float'].HALF_FLOAT_OES :
// gl.UNSIGNED_BYTE;
const e = this.ctx.getExtension('OES_texture_half_float');
t = this.renderer.isWebgl2 ? this.ctx.HALF_FLOAT : e.HALF_FLOAT_OES;
// format = gl.RGBA;
if(this.renderer.isWebgl2) {
internalFormat = this.ctx.RGBA16F;
}
// internalFormat = gl.RGB32F;
// format = gl.RGB32F;
// window.gl = gl
// t = e.HALF_FLOAT_OES;
} else {
t = this.ctx.UNSIGNED_BYTE;
}
const type = t;
const data = this.data;
this.ctx.texImage2D(this.ctx.TEXTURE_2D, level, internalFormat,
this.width*this.pxRatio, this.height*this.pxRatio, border,
format, type, data);
// gl.generateMipmap(gl.TEXTURE_2D);
// set the filtering so we don't need mips
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.NEAREST);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MAG_FILTER, this.ctx.NEAREST);
// Set the parameters based on the passed type
if(this.tiling === Texture.IMAGETYPE_TILE) {
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.REPEAT);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.REPEAT);
} else if(this.tiling === Texture.IMAGETYPE_MIRROR) {
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.MIRRORED_REPEAT);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.MIRRORED_REPEAT);
} else if(this.tiling === Texture.IMAGETYPE_REGULAR) {
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
}
}
// Create and bind the framebuffer
const fb = this.ctx.createFramebuffer();
this.ctx.bindFramebuffer(this.ctx.FRAMEBUFFER, fb);
if(this.depthTesting) {
var ext = this.ctx.getExtension('WEBGL_depth_texture');
let depth = this.ctx.createTexture();
this.ctx.bindTexture(this.ctx.TEXTURE_2D, depth);
this.ctx.texImage2D(
this.ctx.TEXTURE_2D, 0, this.ctx.DEPTH_COMPONENT, this.width*this.pxRatio, this.height*this.pxRatio, 0, this.ctx.DEPTH_COMPONENT, this.ctx.UNSIGNED_SHORT, null);
this.ctx.framebufferTexture2D(this.ctx.FRAMEBUFFER, this.ctx.DEPTH_ATTACHMENT, this.ctx.TEXTURE_2D, depth, 0);
}
// attach the texture as the first color attachment
const attachmentPoint = this.ctx.COLOR_ATTACHMENT0;
const level = 0;
this.ctx.framebufferTexture2D(
this.ctx.FRAMEBUFFER,
attachmentPoint,
this.ctx.TEXTURE_2D,
targetTexture,
level);
return {
fb: fb,
frameTexture: targetTexture
};
}
bind() {
// find the active texture based on the index
let uniform = this.ctx.getUniformLocation(this.renderer.currentProgram.program, `b_${this.#name}`);
// Set the texture unit to the uniform
this.ctx.uniform1i(uniform, this.value);
this.ctx.activeTexture(this.ctx.TEXTURE0 + this.value);
// Bind the texture
this.ctx.bindTexture(this.ctx.TEXTURE_2D, this.#activeFB.frameTexture);
}
render(n) {
this.bind();
// Finally, ping-pong the texture
this.#activeFB = this.#activeFB === this.#fb1 ? this.#fb2 : this.#fb1;
// this.renderer.render(n, this.#activeFB);
this.renderer.render(n, this.#activeFB, [this.width, this.height]);
}
set data(value) {
if(value instanceof Float32Array) this.#data = value;
}
get data() {
return this.#data || null;
}
set width(value) {
if(value > 0) this.#width = value;
}
get width() {
return this.#width || 1;
}
set height(value) {
if(value > 0) this.#height = value;
}
get height() {
return this.#height || 1;
}
set pxRatio(value) {
if(value > 0) this.#pxRatio = value;
}
get pxRatio() {
return this.#pxRatio || 1;
}
set tiling(value) {
if([Texture.IMAGETYPE_REGULAR, Texture.IMAGETYPE_TILE, Texture.IMAGETYPE_MIRROR].indexOf(value) > -1) this.#tiling = value;
}
get tiling() {
return this.#tiling;
}
set texdepth(value) {
if([FrameBuffer.TEXTYPE_FLOAT, FrameBuffer.TEXTYPE_UNSIGNED_BYTE, FrameBuffer.TEXTYPE_HALF_FLOAT_OES].indexOf(value) > -1) this.#texdepth = value;
}
get texdepth() {
return this.#texdepth;
}
}
class Camera {
static #defaultOptions = {
fov: 30 * Math.PI / 180,
aspect: window.innerWidth / window.innerHeight,
near: .5,
far: 100,
pos: new Vec3(3, 1, -5),
target: new Vec3(0, 0, 0),
up: new Vec3(0, 1, 0)
}
#fov
#aspect
#near
#far
#pos
#target
#up
#updateDebounce
#model
#view
#proj
#MVP
#u_model
#u_view
#u_proj
#u_MVP
#q
#name
constructor(renderer, name, options) {
options = Object.assign({}, Camera.#defaultOptions, options);
this.renderer = renderer;
this.ctx = renderer.ctx;
this.fov = options.fov;
this.aspect = options.aspect;
this.near = options.near;
this.far = options.far;
this.pos = options.pos;
this.target = options.target;
this.up = options.up;
this.q = new Quat();
this.name = name;
this.update(true);
}
set q(value) {
if(value instanceof Quat) {
this.#q = value;
this.#model = quatToMat4(this.#q);
this.#u_model = new Uniform(this.ctx, 'm_model', Uniform.TYPE_M4, this.#model.array);
}
}
get q() {
return this.#q || new Quat();
}
update(nt = false) {
clearTimeout(this.#updateDebounce);
// this.#updateDebounce = setTimeout(() => {
this.#model = new Mat4();
this.#view = Mat4.lookAt(this.pos, this.target, this.up);
this.#proj = Mat4.perspective(this.fov, this.aspect, this.near, this.far);
this.#MVP = this.#proj.multiplyNew(this.#view).multiply(this.#model);
this.#u_view = new Uniform(this.ctx, 'm_view', Uniform.TYPE_M4, this.#view.array);
this.#u_proj = new Uniform(this.ctx, 'm_proj', Uniform.TYPE_M4, this.#proj.array);
this.#u_MVP = new Uniform(this.ctx, 'm_MVP', Uniform.TYPE_M4, this.#MVP.array);
this.setup = true;
// }, nt ? 0 : 50);
}
set name(value) {
if(typeof value === 'string') this.#name = value;
}
get name() {
return this.#name || 'camera';
}
set fov(value) {
if(!isNaN(value)) this.#fov = value;
}
get fov() {
return this.#fov;
}
set aspect(value) {
if(!isNaN(value)) this.#aspect = value;
}
get aspect() {
return this.#aspect;
}
set near(value) {
if(!isNaN(value)) this.#near = value;
}
get near() {
return this.#near;
}
set far(value) {
if(!isNaN(value)) this.#far = value;
}
get far() {
return this.#far;
}
set pos(value) {
if(value instanceof Vec3) this.#pos = value;
}
get pos() {
return this.#pos;
}
set target(value) {
if(value instanceof Vec3) this.#target = value;
}
get target() {
return this.#target;
}
set up(value) {
if(value instanceof Vec3) this.#up = value;
}
get up() {
return this.#up;
}
get u_model() {
return this.#u_model;
}
get u_view() {
return this.#u_view;
}
get u_proj() {
return this.#u_proj;
}
get u_MVP() {
return this.#u_MVP;
}
get uniforms() {
return [this.u_model, this.u_view, this.u_proj, this.u_MVP];
}
}
const dimensions = [window.innerWidth, window.innerHeight];
const texturesize = 128;
const particles = texturesize * texturesize;
const textureArraySize = texturesize*texturesize*4.;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const renderer = new Renderer(canvas, { width: dimensions[0], height: dimensions[1], alpha: false, premultipliedAlpha: true, preserveDrawingBuffer: true });
const ctx = renderer.ctx;
let drawing = new Float32Array([ -1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0]);
let vertices = new Float32Array(particles * 6 * 3).fill(0);
for (let i = 0; i < vertices.length; i += 6 * 3) {
vertices[i] = -1;
vertices[i+2] = 1;
vertices[i+1] = 0;
vertices[i+3] = -1;
vertices[i+5] = -1;
vertices[i+4] = 0;
vertices[i+6] = 1;
vertices[i+8] = -1;
vertices[i+7] = 0;
vertices[i+9] = 1;
vertices[i+11] = -1;
vertices[i+10] = 0;
vertices[i+12] = 1;
vertices[i+14] = 1;
vertices[i+13] = 0;
vertices[i+15] = -1;
vertices[i+17] = 1;
vertices[i+16] = 0;
}
window.vertices = vertices;
let references = new Float32Array(particles * 6 * 2).fill(0);
let referenceIndex = 0;
for (let i = 0; i < references.length; i += 6 * 2) {
let index = referenceIndex / 2;
referenceIndex += 2;
const x = (index % texturesize) / texturesize;
const y = Math.floor(index / texturesize) / texturesize;
// console.log(i, '----');
for(let j = 0; j < 6 * 2; j += 2) {
// console.log(i, i + j, i + j + 1);
// console.log(x, y);
// console.log(' ');
references[i + j] = x;
references[i + j + 1] = y;
}
}
const drawBuffer = new Buffer(ctx, drawing);
const particleBuffer = new Buffer(ctx, vertices, {
attributes: [{
name: 'particle',
numComponents: 3
}]
});
const referenceBuffer = new Buffer(ctx, references, {
attributes: [{
name: 'reference',
numComponents: 2
}]
});
const vertexShader_particle = document.getElementById('vertexShader_particle').innerText;
const vertexShader_buffer = document.getElementById('vertexShader_buffer').innerText;
const programPosition = new Program(ctx, vertexShader_buffer, document.getElementById('fragmentShader_position').innerText, {
renderType: Program.RENDER_STRIP });
const programVelocity = new Program(ctx, vertexShader_buffer, document.getElementById('fragmentShader_velocity').innerText, {
renderType: Program.RENDER_STRIP });
const programBuffer = new Program(ctx, vertexShader_buffer, document.getElementById('fragmentShader_buffer').innerText, {
clearColour: [.1,.1,.15, 1],
// blending: Renderer.BLENDING_ADDITIVE,
depthTesting: true,
transparent: false,
renderType: Program.RENDER_STRIP });
const programBlur = new Program(ctx, vertexShader_buffer, document.getElementById('fragmentShader_blur').innerText, {
clearColour: [.1,.1,.15, 0],
renderType: Program.RENDER_STRIP });
const programBloom = new Program(ctx, vertexShader_buffer, document.getElementById('fragmentShader_bloom').innerText, {
clearColour: [.1,.1,.15, 0],
renderType: Program.RENDER_STRIP });
const programMain = new Program(ctx, vertexShader_particle, document.getElementById('fragmentShader_particle').innerText, {
clearColour: [.05, .15, .35, 1.],
// renderType: Program.RENDER_POINTS,
renderType: Program.RENDER_TRIANGLES,
// blending: Renderer.BLENDING_MULTIPLY,
depthTesting: true,
transparent: true
});
const time = new Uniform(ctx, 'time', Uniform.TYPE_FLOAT, 100);
const uDelta = new Uniform(ctx, 'delta', Uniform.TYPE_FLOAT, 100);
const mouse = new Uniform(ctx, 'mouse', Uniform.TYPE_V2, [0.,0.]);
const frame = new Uniform(ctx, 'frame', Uniform.TYPE_INT, 0);
const nsize = new Uniform(ctx, 'nsize', Uniform.TYPE_FLOAT, .1);
const nreset = new Uniform(ctx, 'nreset', Uniform.TYPE_BOOL, false);
const transition = new Uniform(ctx, 'transition', Uniform.TYPE_BOOL, false);
const transition_val = new Uniform(ctx, 'transition_val', Uniform.TYPE_FLOAT, 0);
const bloomstep = new Uniform(ctx, 'bloomstep', Uniform.TYPE_INT, 0);
const seed = new Uniform(ctx, 'seed', Uniform.TYPE_FLOAT, 0);
const originData = new Float32Array(particles * 4).fill(0);
const positionData = new Float32Array(particles * 4).fill(0);
const velocityData = new Float32Array(particles * 4).fill(0);
let bailout = 0.;
const create = (i) => {
let radius = 5.;
let minRadius = 1.;
const rn = Math.random();
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.max(.8, Math.min(Math.cbrt( u ), .85));
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 );
/*
const posit = new Vec2(z, x);
if(Math.abs(y) > .5) {
bailout++;
if(bailout >= 100.) return;
else {
create(i);
return;
}
} else {
bailout = 0.;
}*/
positionData[i] = x;
positionData[i + 1] = y;
positionData[i + 2] = z;
positionData[i + 3] = 1;
originData[i] = x;
originData[i + 1] = y;
originData[i + 2] = z;
originData[i + 3] = 1;
velocityData[i] = x*2;
velocityData[i + 1] = y*2;
velocityData[i + 2] = z*2;
velocityData[i + 3] = 1;
}
for (let i = 0; i < textureArraySize; i += 4) {
create(i);
}
setTimeout(() => {
requestAnimationFrame(run);
}, 10);
const primarybuffer = new FrameBuffer(renderer, 'prime', {
width: dimensions[0],
height: dimensions[1],
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_REGULAR,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: Math.min(window.devicePixelRatio, 2),
depthTesting: true
});
const blurBuffer = new FrameBuffer(renderer, 'blur', {
width: dimensions[0],
height: dimensions[1],
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_REGULAR,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: Math.min(window.devicePixelRatio, 2),
depthTesting: true
});
const bloomBuffer = new FrameBuffer(renderer, 'bloom', {
width: dimensions[0],
height: dimensions[1],
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_REGULAR,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: Math.min(window.devicePixelRatio, 2)
});
const positionBuffer = new FrameBuffer(renderer, 'position', {
width: texturesize,
height: texturesize,
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_TILE,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: 1,
data: positionData
});
const velocityBuffer = new FrameBuffer(renderer, 'velocity', {
width: texturesize,
height: texturesize,
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_TILE,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: 1,
data: velocityData
});
const originBuffer = new FrameBuffer(renderer, 'origin', {
width: texturesize,
height: texturesize,
// tiling: Texture.IMAGETYPE_TILE,
tiling: Texture.IMAGETYPE_TILE,
texdepth: FrameBuffer.TEXTYPE_FLOAT,
pxRatio: 1,
data: originData
});
const cp = new Vec3(-25, 75, 100);
cp.length = 100;
const compositions = [
{
pos: new Vec3(0, 0, 40),
target: new Vec3(0,0,0),
endPos: new Vec3(0, 3, 30),
endTarget: new Vec3(0,0,0),
time: 50
},
{
pos: new Vec3(0, -30, 30),
target: new Vec3(0, 30, 0),
endPos: new Vec3(0, -30, 0),
endTarget: new Vec3(0, 30, 0),
time: 20
},
{
pos: new Vec3(0, 100, 0),
target: new Vec3(0, 0, 0),
endPos: new Vec3(0, 20, 40),
endTarget: new Vec3(0, 20, -20),
time: 40
},
{
pos: new Vec3(0, 20, 40),
target: new Vec3(15, 0, -20),
endPos: new Vec3(0, 20, 40),
endTarget: new Vec3(-15, 0, -20),
time: 20
},
{
pos: new Vec3(0, 20, -60),
target: new Vec3(5, 0, 0),
endPos: new Vec3(0, 30, 50),
endTarget: new Vec3(5, 0, 0),
time: 50
},
{
pos: new Vec3(0, -20, 0),
target: new Vec3(0, 0, 0),
endPos: new Vec3(10, 80, 10),
endTarget: new Vec3(0, 0, 0),
time: 40
},
{
pos: new Vec3(30, 0, 0),
target: new Vec3(20, 0, 0),
endPos: new Vec3(30, 0, 30),
endTarget: new Vec3(0, 0, 0),
time: 15
},
{
pos: new Vec3(80, 0, 0),
target: new Vec3(0, 0, 0),
endPos: new Vec3(0, 0, 80),
endTarget: new Vec3(0, 0, 0),
time: 40
},
{
pos: new Vec3(0, 10, 0),
target: new Vec3(0, 0, 0),
endPos: new Vec3(10, 10, 60),
endTarget: new Vec3(0, 0, 0),
time: 40
},
{
pos: cp.clone().scaleNew(.2),
target: new Vec3(0, 0, 0),
endPos: cp.clone(),
endTarget: new Vec3(0, 0, 0),
time: 30
},
{
pos: new Vec3(0, 2, 30),
target: new Vec3(-5, 0, 0),
endPos: new Vec3(0, 5, -20),
endTarget: new Vec3(0, 0, 0),
time: 30
},
{
pos: new Vec3(0, 5, 0),
target: new Vec3(0,0,0),
endPos: new Vec3(0, 40, 5),
endTarget: new Vec3(0,0,0),
time: 30
}
];
const cam = new Camera(renderer, 'main', { fov: 1.8, far: 500, aspect: dimensions[0]/dimensions[1] });
let camop = compositions[0];
let camtime = 0;
let pointerdown = false;
let pointermoving = false;
let pointerTimeout;
let lastPos = new Vec2();
window.addEventListener('pointerdown', (e) => {
pointerdown = true;
pointerTimeout = setTimeout(() => {
pointermoving = true;
}, 200)
lastPos = new Vec2(e.x, e.y);
});
window.addEventListener('pointerup', (e) => {
pointerdown = false;
pointerTimeout = setTimeout(() => {
pointermoving = false;
}, 0)
clearTimeout(pointerTimeout);
});
window.addEventListener('pointermove', (e) => {
if(pointerdown) {
let newPos = new Vec2(e.x, e.y);
let diff = newPos.clone().subtract(lastPos);
lastPos = newPos.clone();
let q = cam.q;
q.rotateY(diff.x * -.01);
q.rotateX(diff.y * -.01);
cam.q = q;
}
// let q = cam.q;
// this.#u_model = new Uniform(this.ctx, 'm_model', Uniform.TYPE_M4, this.#model.array);
});
let playing = true;
const setPlaying = (value) => {
playing = value;
}
const playpause = document.querySelector('.playpause');
const playpauseCheck = document.querySelector('.playpause input');
playpauseCheck.addEventListener('change', (e)=> {
playpause.classList.toggle('checked', e.target.checked);
setPlaying(e.target.checked);
});
let autoTransitionTimer = 0;
let timeToTransition = 0;
const setupValues = (i) => {
transition.value = false;
nsize.value = Math.random() * .5 + .1;
nreset.value = true;
dimensions[0] = window.innerWidth;
dimensions[1] = window.innerHeight;
cam.aspect = dimensions[0]/dimensions[1];
console.log(i);
// camop = compositions[i];,
camop = {
pos: compositions[i].pos.scaleNew(.5),
target: compositions[i].target.scaleNew(.5),
endPos: compositions[i].endPos.scaleNew(.5),
endTarget: compositions[i].endTarget.scaleNew(.5),
time: compositions[i].time
}
cam.pos = camop.pos;
cam.target = camop.target;
cam.update(true);
cam.q = new Quat();
camtime = 0;
renderer.resize(dimensions[0], dimensions[1]);
primarybuffer.resize(dimensions[0], dimensions[1]);
blurBuffer.resize(dimensions[0], dimensions[1]);
time.value = -10000;
seed.value = Math.random() * -1000.;
setTimeout(() => {
seed.value += Math.random() * .5;
}, 5000);
timeToTransition = camop.time*1000;
autoTransitionTimer = 0;
// autoTransitionTimer = setTimeout(setupTransition, );
}
const setupTransition = () => {
if(transition.value === true) return;
clearTimeout(autoTransitionTimer);
transition.value = true;
transition_val.value = 0;
setTimeout(() => {
setupValues(Math.floor(Math.random()*compositions.length));
}, 2000);
}
setupValues(0);
let oldpos = 0;
canvas.addEventListener('click', (e) => {
if(playing !== true) return;
if(pointermoving === true) return;
setupTransition();
});
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
dimensions[0] = window.innerWidth;
dimensions[1] = window.innerHeight;
cam.aspect = dimensions[0]/dimensions[1];
cam.update(true);
renderer.resize(dimensions[0], dimensions[1]);
primarybuffer.resize(dimensions[0], dimensions[1]);
blurBuffer.resize(dimensions[0], dimensions[1]);
bloomBuffer.resize(dimensions[0], dimensions[1]);
}, 100);
});
const cubeInOut = d => {
if (d < 0.5) return 4 * d**3;
return 1 - (-2 * d + 2)**3 * 0.5;
}
const opx = renderer.pxRatio;
let then = 0;
// let framenum = 0;
// let framesPerFrame = 10;
// let gif = new gifJs({
// workers: 2,
// quality: 10
// });
// gif.on('finished', function(blob) {
// console.log('ss')
// window.open(URL.createObjectURL(blob));
// });
// const offscreenCanvas = document.createElement("canvas");
// offscreenCanvas.className = 'osc';
// offscreenCanvas.width = canvas.width;
// offscreenCanvas.height = canvas.height;
// const osctx = offscreenCanvas.getContext("2d");
// document.body.appendChild(offscreenCanvas);
let gifDone = false;
const run = (delta) => {
// if(framenum < 10 * framesPerFrame) {
// if(framenum % framesPerFrame == 0) {
// // gif.addFrame(canvas, {delay: 100});
// osctx.drawImage(canvas,0,0);
// gif.addFrame(offscreenCanvas, {copy: true, delay: 100});
// // gif.addFrame(ctx, {copy: true});
// }
// framenum++;
// } else if(gifDone === false) {
// console.log(framenum)
// gif.render();
// window.gif = gif;
// gifDone = true;
// }
let now = Date.now() / 1000;
let _delta = now - then;
then = now;
if(_delta > 1000) {
requestAnimationFrame(run);
return;
}
if(playing) {
// timeToTransition = camop.time*1000;
autoTransitionTimer += _delta*1000;
if(autoTransitionTimer > timeToTransition) {
timeToTransition = Infinity;
setupTransition();
}
bloomstep.value = 0;
uDelta.value = Math.min(_delta, 0.5);
time.value += _delta * .05;
frame.value += 1;
if(transition.value === true) {
transition_val.value += _delta * .5;
} else if(transition_val.value >= 0.) {
transition_val.value -= _delta * .5;
}
camtime += _delta;
if(camtime <= camop.time && camop.endPos) {
let ntime = Math.min(camtime/camop.time, 1.);
let tween = cubeInOut(ntime);
cam.pos = Vec3.lerp(camop.pos, camop.endPos, tween);
cam.target = Vec3.lerp(camop.target, camop.endTarget, tween);
cam.update(true);
}
renderer.setViewport([velocityBuffer.width, velocityBuffer.height]);
renderer.setupProgram(programVelocity, [drawBuffer], [], [time, frame, mouse, velocityBuffer, positionBuffer, nsize, seed]);
velocityBuffer.render(4);
renderer.setupProgram(programPosition, [drawBuffer], [], [time, frame, mouse, velocityBuffer, positionBuffer, uDelta, originBuffer, nreset]);
positionBuffer.render(4);
}
renderer.setViewport();
renderer.setupProgram(programMain, [particleBuffer, referenceBuffer], [], [time, frame, mouse, velocityBuffer, primarybuffer, positionBuffer, ...cam.uniforms, transition, transition_val]);
primarybuffer.render(particles * 6);
// renderer.render(particles * 6);
renderer.setupProgram(programBlur, [drawBuffer], [], [time, mouse, primarybuffer, positionBuffer, blurBuffer]);
blurBuffer.render(4);
// bloomstep.value = 0;
// renderer.setupProgram(programBloom, [drawBuffer], [], [time, mouse, primarybuffer, positionBuffer, blurBuffer, bloomstep, bloomBuffer]);
// bloomBuffer.render(4);
// bloomstep.value = 1;
// renderer.setupProgram(programBloom, [drawBuffer], [], [time, mouse, primarybuffer, positionBuffer, blurBuffer, bloomstep, bloomBuffer]);
// bloomBuffer.render(4);
renderer.setupProgram(programBuffer, [drawBuffer], [], [time, mouse, primarybuffer, positionBuffer, bloomBuffer, blurBuffer, transition, transition_val]);
renderer.render(4);
nreset.value = false;
requestAnimationFrame(run);
}