Мозайка Вороного
Мозайка Вороного с использованием шейдеров
HTML
<!-- VertexShader code here -->
<script id="vertexShader" type="x-shader/x-vertex">#version 300 es
in vec4 vPosition;
void main() {
gl_Position = vPosition;
}
</script>
<!-- FragmentShader code here -->
<script id="fragmentShader" type="x-shader/x-fragment">#version 300 es
precision mediump float;
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
uniform vec4 mouse;
uniform float zoom;
uniform float show;
uniform vec3 colorA;
uniform vec3 colorB;
uniform vec3 colorC;
#define PI 3.1415926
#define PI2 6.2831853
vec3 normal_color( vec3 x ) {
return (x-min(x,1.))/(max(x,255.)-min(x,1.));
}
vec2 hash2( vec2 p ) {
return fract(
sin(
vec2(
dot(p,vec2(44.3,25.7)),
dot(p,vec2(87.2,54.1)))
)*4258.4373);
}
vec2 get_mouse(void) {
float ax = 10.*mouse.x/resolution.x;
float ay = 10.*mouse.y/resolution.y;
return (mouse.xy==vec2(0)) ? vec2(0.3,0.4) : vec2(ax,ay);
}
// http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm
// updated to a vec4 to return hash of object in grid
vec4 voronoi( in vec2 x )
{
vec2 mouse = get_mouse();
x.x += mouse.x*.55;
x.y += mouse.y*.25;
float wave = 13.4 + (time*.65) + mouse.x; // mouse.x; // time;
vec2 n = floor(x);
vec2 f = fract(x);
float ox = 0.;
vec2 mg, mr;
float md = 8.;
for( int j=-1; j<=1; j++ )
for( int i=-1; i<=1; i++ )
{
vec2 g = vec2(float(i),float(j));
vec2 o = hash2( n + g );
ox = o.x;
o = .5 + .5 *sin(o * wave + PI2);
vec2 r = g + o - f;
float d = dot(r,r);
if( d<md )
{
md = d;
mr = r;
mg = g;
}
}
md = 8.;
for( int j=-2; j<=2; j++ )
for( int i=-2; i<=2; i++ )
{
vec2 g = mg + vec2(float(i),float(j));
vec2 o = hash2( n + g );
ox = o.x;
o = .5 + .5 *sin(o * wave + PI2);
vec2 r = g + o - f;
if( dot(mr-r,mr-r)>0.00001 )
md = min( md, dot( 0.5*(mr+r), normalize(r-mr) ) );
}
return vec4( md, mr, ox );
}
void main(void)
{
vec2 uv = gl_FragCoord.xy/max(resolution.x,resolution.y);
// for fun effect
// uv = abs(uv-vec2(.5,.27));
vec2 buv = uv;
uv.y -= time * .05;
buv.y -= time * .053;
float back_zoom = zoom * 4.2;
uv *= zoom;
buv *= back_zoom;
vec2 uid = vec2(
floor(uv.x),
floor(uv.x)
);
vec4 c = voronoi( uv );
vec4 inset = voronoi( uv + vec2(.09,.06));
vec4 backv = voronoi( buv);
vec3 sle = vec3(c.z - c.x);
vec3 sne = vec3(c.y - c.x);
vec3 col = vec3(1.);
vec3 mate = vec3(1.);
vec3 dmate = vec3(1.);
// color top layer
if(c.w<.25){
mate = normal_color(colorC);
} else if (c.w<.5) {
mate = normal_color(colorB);
} else if (c.w<.75) {
mate = normal_color(colorA);
} else {
if(show<1.){
vec2 f=fract(buv.xy * .75 +time)-0.5;
float checkrd = f.x*f.y>0.0?0.5:0.25;
mate *= checkrd;
} else {
mate = vec3( .8 );
}
}
// color bottom layer
if(backv.w<.25){
dmate = normal_color(colorC);
} else if (backv.w<.5) {
dmate = normal_color(colorA);
} else if (backv.w<.75) {
dmate = normal_color(colorB);
} else {
dmate = vec3( .8 );
}
float diag = clamp(sin((uv.x - uv.y)*PI2*30.)*1. + .95, 0., 1.)*.08 + .08;
// background voronoi pattern
vec3 bkgnd = smoothstep( 0.01, 0.05, backv.x) * dmate;
float border = 1.-smoothstep( 0.04, 0.05, c.x);
bkgnd = min(bkgnd,1.-border); // cut the background away from border
// shade which has the inner drop shadow
vec3 shade = smoothstep( -.01, 0.13, inset.x) * mate - diag;
shade = min(shade,1.-border); // cut the shade away from border
// highlights and offsets
vec3 hglt = clamp(sne + sle, 0., 1.);
// Check for background pattern
bkgnd = show < 1. ? vec3(.8) * diag : bkgnd;
// Layering is hard and still quite frustating.. but this is pretty!
col = (border*.15) + bkgnd * sne + shade + border * sin(sle*2.4) - hglt*.5;
// Solo variations
//col = vec3(4.5) * shade * diag + border * sin(sle*2.4);
fragColor = vec4(col,1.);
}
</script>
LESS
html {
height: 100%;
}
body {
background: #999;
font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;
font-size: 16px;
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
JS
дополнительные скрипты:https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.0/dat.gui.min.js
https://codepen.io/pjkarlik/pen/791ef12e0bbed18e0305be528fbf623d
скрипт// Render Class //
// Boostrap for WebGL and Attaching Shaders //
// Fragment & Vertex Shaders in HTML window //
class Render {
constructor() {
this.zoom = 6.75;
this.showBackground = true;
this.color1 = [150, 102, 73];
this.color2 = [89, 96, 107];
this.color3 = [89, 95, 88];
this.start = Date.now();
this.mouse = new Mouse();
this.umouse = [0.0, 0.0, 0.0, 0.0];
this.tmouse = [0.0, 0.0, 0.0, 0.0];
const mouse = this.mouse.pointer();
// Setup WebGL canvas and surface object //
// Make Canvas and get WebGl2 Context //
const width = (this.width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0));
const height = (this.height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0));
const canvas = (this.canvas = document.createElement("canvas"));
canvas.id = "GLShaders";
canvas.width = width;
canvas.height = height;
document.body.appendChild(canvas);
const gl = (this.gl = canvas.getContext("webgl2"));
if (!gl) {
console.warn("WebGL 2 is not available.");
return;
}
// WebGl and WebGl2 Extension //
this.gl.getExtension("OES_standard_derivatives");
this.gl.getExtension("EXT_shader_texture_lod");
this.gl.getExtension("OES_texture_float");
this.gl.getExtension("WEBGL_color_buffer_float");
this.gl.getExtension("OES_texture_float_linear");
this.gl.viewport(0, 0, canvas.width, canvas.height);
window.addEventListener(
"resize",
() => {
const width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0);
const height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0);
this.canvas.width = width;
this.canvas.height = height;
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.resolution = new Float32Array([width, height]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program, "resolution"),
this.resolution
);
this.clearCanvas();
},
true
);
this.init();
}
// Canvas Helper Function //
createCanvas = name => {
this.canvas =
document.getElementById(name) || document.createElement("canvas");
this.canvas.id = name;
if (!document.getElementById(name)) {
document.body.appendChild(this.canvas);
}
const context = this.canvas.getContext("webgl2");
if (!context) {
console.error("no webgl avaiable");
}
this.setViewport();
};
// Viewport Helper Function //
setViewport = () => {
this.width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0);
this.height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0);
this.gl = this.canvas.getContext("webgl");
this.canvas.width = this.width;
this.canvas.height = this.height;
this.gl.viewport(0, 0, this.width, this.height);
this.clearCanvas();
};
// Shader Bootstrap code //
createShader = (type, source) => {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
const success = this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS);
if (!success) {
console.log(this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return false;
}
return shader;
};
createWebGL = (vertexSource, fragmentSource) => {
// Setup Vertext/Fragment Shader functions
this.vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
this.fragmentShader = this.createShader(
this.gl.FRAGMENT_SHADER,
fragmentSource
);
// Setup Program and Attach Shader functions
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, this.vertexShader);
this.gl.attachShader(this.program, this.fragmentShader);
this.gl.linkProgram(this.program);
this.gl.useProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.warn(
"Unable to initialize the shader program: " +
this.gl.getProgramInfoLog(this.program)
);
return null;
}
// Create and Bind buffer //
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]),
this.gl.STATIC_DRAW
);
const vPosition = this.gl.getAttribLocation(this.program, "vPosition");
this.gl.enableVertexAttribArray(vPosition);
this.gl.vertexAttribPointer(
vPosition,
2, // size: 2 components per iteration
this.gl.FLOAT, // type: the data is 32bit floats
false, // normalize: don't normalize the data
0, // stride: 0 = move forward size * sizeof(type) each iteration to get the next position
0 // start at the beginning of the buffer
);
this.clearCanvas();
this.importUniforms();
};
clearCanvas = () => {
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
};
importUniforms = () => {
const width = ~~(document.documentElement.clientWidth,
window.innerWidth || 0);
const height = ~~(document.documentElement.clientHeight,
window.innerHeight || 0);
this.resolution = new Float32Array([width, height]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program, "resolution"),
this.resolution
);
// get the uniform ins from the shader fragments
this.ut = this.gl.getUniformLocation(this.program, "time");
this.ms = this.gl.getUniformLocation(this.program, "mouse");
this.zm = this.gl.getUniformLocation(this.program, "zoom");
this.c1 = this.gl.getUniformLocation(this.program, "colorA");
this.c2 = this.gl.getUniformLocation(this.program, "colorB");
this.c3 = this.gl.getUniformLocation(this.program, "colorC");
this.sb = this.gl.getUniformLocation(this.program, "show");
this.resolution = new Float32Array([this.canvas.width, this.canvas.height]);
this.gl.uniform2fv(
this.gl.getUniformLocation(this.program, "resolution"),
this.resolution
);
this.gl.uniform4fv(this.ms, this.tmouse);
this.gl.uniform1f(this.zm, this.zoom);
this.gl.uniform1f(this.sb, this.showBackground);
this.gl.uniform3fv(this.c1, this.color1);
this.gl.uniform3fv(this.c2, this.color2);
this.gl.uniform3fv(this.c3, this.color3);
};
updateUniforms = () => {
this.gl.uniform1f(this.ut, (Date.now() - this.start) / 1000);
const mouse = this.mouse.pointer();
this.umouse = [mouse.x, this.canvas.height - mouse.y, mouse.x - mouse.y];
const factor = 0.15;
this.tmouse[0] =
this.tmouse[0] - (this.tmouse[0] - this.umouse[0]) * factor;
this.tmouse[1] =
this.tmouse[1] - (this.tmouse[1] - this.umouse[1]) * factor;
this.tmouse[2] =
this.tmouse[2] - (this.tmouse[2] - this.umouse[2]) * factor;
this.gl.uniform4fv(this.ms, this.tmouse);
this.gl.drawArrays(
this.gl.TRIANGLE_FAN, // primitiveType
0, // Offset
4 // Count
);
};
init = () => {
this.createWebGL(
document.getElementById("vertexShader").textContent,
document.getElementById("fragmentShader").textContent
);
this.createGUI();
this.renderLoop();
};
// DatGUI Bootstrap code //
createGUI = () => {
this.options = {
zoom: this.zoom,
color1: this.color1,
color2: this.color2,
color3: this.color3,
showBackground: this.showBackground
};
this.gui = new dat.GUI();
const folderRender = this.gui.addFolder("Render Options");
folderRender
.add(this.options, "zoom", 1., 50.)
.step(0.01)
.onFinishChange(value => {
this.zoom = value;
this.gl.uniform1f(this.zm, this.zoom);
});
folderRender
.add(this.options, "showBackground")
.onFinishChange(value => {
this.showBackground = value;
this.gl.uniform1f(this.sb, this.showBackground);
});
folderRender.addColor(this.options, "color1").onchange(value => {
this.color1 = value;
this.gl.uniform3fv(this.c1, this.color1);
});
folderRender.addColor(this.options, "color2").onchange(value => {
this.color2 = value;
this.gl.uniform3fv(this.c2, this.color2);
});
folderRender.addColor(this.options, "color3").onchange(value => {
this.color3 = value;
this.gl.uniform3fv(this.c3, this.color3);
});
folderRender.open();
};
renderLoop = () => {
this.updateUniforms();
this.animation = window.requestAnimationFrame(this.renderLoop);
};
}
const demo = new Render(document.body);