Responsive canvas using webgl and meatballs.js - javascript

I'm attempting to implement this codepen as a background of my personal website. I have no real knowledge of WebGL, so please bear with me. I temporarily added an event listener to update the width and height of the canvas when the page is resized. I can tell this works because when the bubbles start going out of bounds, they continue going and don't bounce off the edge of the page, so I know it somewhat works the way I want it to. When the fragment shader source is defined it also defines the width and height and I'm not sure how to change those variables after that. I tried redefining, recompiling, and reattaching the fragment shader source with the new widths and heights. This obviously doesn't work because the bubbles do not render past the size of the page when the canvas was created. I'm not sure if i'm even going about this the right way, if so what am I doing wrong? All/any help is appreciated, thank you.
The code I changed:
var canvas = document.createElement("canvas");
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var mouse = {x: 0, y: 0};
var numMetaballs = 30;
var metaballs = [];
var first = true
window.addEventListener('resize', function(){
width = canvas.width = window.innerWidth * 0.75;
height = canvas.height = window.innerHeight * 0.75;
shaderStuff()
})
function shaderStuff(){
if(!first) {
gl.detachShader(program, gl.getAttachedShaders(program)[1])
}
first = false
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < ` + numMetaballs + `; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0, 0, 0, 0);
}
`;
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
gl.attachShader(program, fragmentShader);
}
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs.push({
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
});
}
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
shaderStuff()
gl.linkProgram(program);
gl.useProgram(program);
The whole project https://meatballsjs.000webhostapp.com/
The original https://codepen.io/TC5550/pen/WNNWoaO

The easiest way would be to put all of the background creation code in a function, and call it every time the page is resized.
You will also need to add some code to cause the previous background loops to stop, and you should add some throttling to prevent too many backgrounds to be created at once.
This is somewhat inefficient, but most users don't expect applications to be extremely responsive while they are being resized, and resizing is an infrequent operation.
I added a code snippet, which appears to work, however I could not get my changes to work in codepen. I believe this is because codepen instruments and modifies the code in a certain way that breaks it (jsbin has similar behavior to prevent infinite loops, and to sandbox it). However I tested my changes in just a .html file, and they seemed to work there, so they should work on your site.
On a side note, very cool use of WebGL!
var nextBackgroundId = 1;
var currentBackgroundId = 0;
setupBackground(currentBackgroundId);
window.addEventListener("resize", () => {
var ourBackgroundId = nextBackgroundId++;
currentBackgroundId = ourBackgroundId;
setTimeout(() => {
setupBackground(ourBackgroundId);
}, 100);
});
function setupBackground(ourBackgroundId) {
if (currentBackgroundId !== ourBackgroundId) {
return;
}
var prevCanvas = document.getElementById("blob-canvas");
if (prevCanvas) {
prevCanvas.remove();
}
var canvas = document.createElement("canvas");
canvas.id = "blob-canvas";
var mouse = { x: 0, y: 0 };
canvas.onmousemove = function (e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
}
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var numMetaballs = 30;
var metaballs = [];
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs.push({
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
});
}
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < ` + numMetaballs + `; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
var vertexData = new Float32Array([
-1.0, 1.0, // top left
-1.0, -1.0, // bottom left
1.0, 1.0, // top right
1.0, -1.0, // bottom right
]);
var vertexDataBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
var positionHandle = getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionHandle);
gl.vertexAttribPointer(positionHandle,
2, // position is a vec2
gl.FLOAT, // each component is a float
gl.FALSE, // don't normalize values
2 * 4, // two 4 byte float components per vertex
0 // offset into each span of vertex data
);
var metaballsHandle = getUniformLocation(program, 'metaballs');
loop();
function loop() {
if (currentBackgroundId !== ourBackgroundId) {
return;
}
for (var i = 0; i < numMetaballs; i++) {
var metaball = metaballs[i];
metaball.x += metaball.vx;
metaball.y += metaball.vy;
if (metaball.x < metaball.r || metaball.x > width - metaball.r) metaball.vx *= -1;
if (metaball.y < metaball.r || metaball.y > height - metaball.r) metaball.vy *= -1;
}
var dataToSendToGPU = new Float32Array(3 * numMetaballs);
for (var i = 0; i < numMetaballs; i++) {
var baseIndex = 3 * i;
var mb = metaballs[i];
dataToSendToGPU[baseIndex + 0] = mb.x;
dataToSendToGPU[baseIndex + 1] = mb.y;
dataToSendToGPU[baseIndex + 2] = mb.r;
}
gl.uniform3fv(metaballsHandle, dataToSendToGPU);
//Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(loop);
}
function compileShader(shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
}
return shader;
}
function getUniformLocation(program, name) {
var uniformLocation = gl.getUniformLocation(program, name);
if (uniformLocation === -1) {
throw 'Can not find uniform ' + name + '.';
}
return uniformLocation;
}
function getAttribLocation(program, name) {
var attributeLocation = gl.getAttribLocation(program, name);
if (attributeLocation === -1) {
throw 'Can not find attribute ' + name + '.';
}
return attributeLocation;
}
}
body {
font-family: 'Alatsi', sans-serif;
margin: 0;
overflow: hidden;
background: black;
}
.container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.title {
font-size: 10vw;
color: white;
}
canvas {
width: 100%;
}
<div class="container">
<span class="title">MEATBALLS</span>
</div>

There are a lot of issues with that codepen.
It's hacking the canvas size instead of letting CSS size the canvas.
In the code the canvas size is set with
var width = canvas.width = window.innerWidth * 0.75;
var height = canvas.height = window.innerHeight * 0.75;
It's arguably best to let the browser size the canvas
html, body {
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
and then ask the browser what size the canvas is and set the canvas's resolution to match
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
It's asking for things are larger than the window so it would get a scrollbar and then hiding the that fact by hiding the scrollbar. It makes no sense. If you don't want a scrollbar don't ask for content that requires a scrollbar.
html, body {
height: 100%;
/* removed overflow: hidden */
}
canvas {
width: 100%;
height: 100%;
display: block;
}
It's using template strings but not actually using them as templates
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ` + (width >> 0) + `.0;
const float HEIGHT = ` + (height >> 0) + `.0;
uniform vec3 metaballs[` + numMetaballs + `];
...
`;
should arguably be
var fragmentShaderSrc = `
precision highp float;
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
uniform vec3 metaballs[${numMetaballs}];
...
`;
The main point of using backticks for strings is so you can
use the templating feature ${code}
It's hard coding the width and height
const float WIDTH = ${width >> 0}.0;
const float HEIGHT = ${height >> 0}.0;
should arguably be
uniform float WIDTH;
uniform float HEIGHT;
so they can be set
Metaballs is misspelled as Meatballs (maybe that was intentional)
Here's a new version. Note: anytime the window is resized the metaballs get a random position. If you comment out the call to updateMetaballs after resizing the canvas then they will not get new random positions. Which is better is up to you. The logic for how they bounce is such that any balls that are off the screen after you resize will stay off the screen. You could fix it so they'll head toward the screen and only bounce from the inside back in. The current code is such that on the outside they'll just wobble where they are.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var gl = canvas.getContext('webgl');
var mouse = {x: 0, y: 0};
var numMetaballs = 30;
var metaballs = [];
function updateMetaballs() {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
for (var i = 0; i < numMetaballs; i++) {
var radius = Math.random() * 60 + 10;
metaballs[i] = {
x: Math.random() * (width - 2 * radius) + radius,
y: Math.random() * (height - 2 * radius) + radius,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
r: radius * 0.75
};
}
}
updateMetaballs();
var vertexShaderSrc = `
attribute vec2 position;
void main() {
// position specifies only x and y.
// We set z to be 0.0, and w to be 1.0
gl_Position = vec4(position, 0.0, 1.0);
}
`;
var fragmentShaderSrc = `
precision highp float;
uniform float WIDTH;
uniform float HEIGHT;
#define NUM_METABALLS ${numMetaballs}
uniform vec3 metaballs[NUM_METABALLS];
void main(){
float x = gl_FragCoord.x;
float y = gl_FragCoord.y;
float sum = 0.0;
for (int i = 0; i < NUM_METABALLS; i++) {
vec3 metaball = metaballs[i];
float dx = metaball.x - x;
float dy = metaball.y - y;
float radius = metaball.z;
sum += (radius * radius) / (dx * dx + dy * dy);
}
if (sum >= 0.99) {
gl_FragColor = vec4(mix(vec3(x / WIDTH, y / HEIGHT, 1.0), vec3(0, 0, 0), max(0.0, 1.0 - (sum - 0.99) * 100.0)), 1.0);
return;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
var vertexShader = compileShader(vertexShaderSrc, gl.VERTEX_SHADER);
var fragmentShader = compileShader(fragmentShaderSrc, gl.FRAGMENT_SHADER);
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
var vertexData = new Float32Array([
-1.0, 1.0, // top left
-1.0, -1.0, // bottom left
1.0, 1.0, // top right
1.0, -1.0, // bottom right
]);
var vertexDataBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
var positionHandle = getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionHandle);
gl.vertexAttribPointer(positionHandle,
2, // position is a vec2
gl.FLOAT, // each component is a float
gl.FALSE, // don't normalize values
2 * 4, // two 4 byte float components per vertex
0 // offset into each span of vertex data
);
var metaballsHandle = getUniformLocation(program, 'metaballs');
var widthHandle = getUniformLocation(program, 'WIDTH');
var heightHandle = getUniformLocation(program, 'HEIGHT');
function resizeCanvasToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
canvas.width = width;
canvas.height = height;
}
return needResize;
}
loop();
function loop() {
if (resizeCanvasToDisplaySize(canvas)) {
updateMetaballs();
}
const {width, height} = canvas;
gl.viewport(0, 0, canvas.width, canvas.height);
for (var i = 0; i < numMetaballs; i++) {
var metaball = metaballs[i];
metaball.x += metaball.vx;
metaball.y += metaball.vy;
if (metaball.x < metaball.r || metaball.x > width - metaball.r) metaball.vx *= -1;
if (metaball.y < metaball.r || metaball.y > height - metaball.r) metaball.vy *= -1;
}
var dataToSendToGPU = new Float32Array(3 * numMetaballs);
for (var i = 0; i < numMetaballs; i++) {
var baseIndex = 3 * i;
var mb = metaballs[i];
dataToSendToGPU[baseIndex + 0] = mb.x;
dataToSendToGPU[baseIndex + 1] = mb.y;
dataToSendToGPU[baseIndex + 2] = mb.r;
}
gl.uniform3fv(metaballsHandle, dataToSendToGPU);
gl.uniform1f(widthHandle, canvas.clientWidth);
gl.uniform1f(heightHandle, canvas.clientHeight);
//Draw
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(loop);
}
function compileShader(shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
}
return shader;
}
function getUniformLocation(program, name) {
var uniformLocation = gl.getUniformLocation(program, name);
if (uniformLocation === -1) {
throw 'Can not find uniform ' + name + '.';
}
return uniformLocation;
}
function getAttribLocation(program, name) {
var attributeLocation = gl.getAttribLocation(program, name);
if (attributeLocation === -1) {
throw 'Can not find attribute ' + name + '.';
}
return attributeLocation;
}
canvas.onmousemove = function(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
}
html, body {
font-family: 'Alatsi', sans-serif;
margin: 0;
background: black;
height: 100%;
}
.container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.title {
font-size: 10vw;
color: white;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<div class="container">
<span class="title">METABALLS</span>
</div>
If you want to learn WebGL consider these tutorials

Related

how do i make the html code appear on top of the animation and not behind it

I tried putting this in CSS p{ z-index: 99 !important; } it didn't work
I Just want to make everything in html appear on top of the animation
here is the code::
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<div id="container">
<p>hellpo</p>
</div>
<script id="vertex-shader" type="no-js">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragment-shader" type="no-js">
uniform float iGlobalTime;
uniform vec2 iResolution;
const int NUM_STEPS = 8;
const float PI = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM = 0.1 / iResolution.x;
// sea variables
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 1.0;
const float SEA_SPEED = 1.0;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
float SEA_TIME = iGlobalTime * SEA_SPEED;
mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
mat3 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat3 m;
m[0] = vec3(
a1.y*a3.y+a1.x*a2.x*a3.x,
a1.y*a2.x*a3.x+a3.y*a1.x,
-a2.y*a3.x
);
m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
m[2] = vec3(
a3.y*a1.x*a2.x+a1.y*a3.x,
a1.x*a3.x-a1.y*a3.y*a2.x,
a2.y*a3.y
);
return m;
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return -1.0 + 2.0 * mix(
mix(
hash(i + vec2(0.0,0.0)
),
hash(i + vec2(1.0,0.0)), u.x),
mix(hash(i + vec2(0.0,1.0) ),
hash(i + vec2(1.0,1.0) ), u.x),
u.y
);
}
float diffuse(vec3 n,vec3 l,float p) {
return pow(dot(n,l) * 0.4 + 0.6,p);
}
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (3.1415 * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
vec3 getSkyColor(vec3 e) {
e.y = max(e.y, 0.0);
vec3 ret;
ret.x = pow(1.0 - e.y, 2.0);
ret.y = 1.0 - e.y;
ret.z = 0.6+(1.0 - e.y) * 0.4;
return ret;
}
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0 - abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv, swv, wv);
return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz;
uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv + SEA_TIME) * freq, choppy);
d += sea_octave((uv - SEA_TIME) * freq, choppy);
h += d * amp;
uv *= octave_m;
freq *= 1.9;
amp *= 0.22;
choppy = mix(choppy, 1.0, 0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz;
uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME) * freq, choppy);
d += sea_octave((uv-SEA_TIME) * freq, choppy);
h += d * amp;
uv *= octave_m;
freq *= 1.9;
amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 getSeaColor(
vec3 p,
vec3 n,
vec3 l,
vec3 eye,
vec3 dist
) {
float fresnel = 1.0 - max(dot(n,-eye),0.0);
fresnel = pow(fresnel,3.0) * 0.65;
vec3 reflected = getSkyColor(reflect(eye,n));
vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;
vec3 color = mix(refracted,reflected,fresnel);
float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
color += vec3(specular(n,l,eye,60.0));
return color;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) {
return tx;
}
float hm = map(ori + dir * tm);
float tmid = 0.0;
for(int i = 0; i < NUM_STEPS; i++) {
tmid = mix(tm,tx, hm/(hm-hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
}
return tmid;
}
void main() {
vec2 uv = gl_FragCoord.xy / iResolution.xy;
uv = uv * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
float time = iGlobalTime * 0.3;
// ray
vec3 ang = vec3(
sin(time*3.0)*0.1,sin(time)*0.2+0.3,time
);
vec3 ori = vec3(0.0,3.5,time*5.0);
vec3 dir = normalize(
vec3(uv.xy,-2.0)
);
dir.z += length(uv) * 0.15;
dir = normalize(dir);
// tracing
vec3 p;
heightMapTracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(
p,
dot(dist,dist) * EPSILON_NRM
);
vec3 light = normalize(vec3(0.0,1.0,0.8));
// color
vec3 color = mix(
getSkyColor(dir),
getSeaColor(p,n,light,dir,dist),
pow(smoothstep(0.0,-0.05,dir.y),0.3)
);
// post
gl_FragColor = vec4(pow(color,vec3(0.75)), 1.0);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="script.js"></script>
</body>
</html>
css
body {
overflow: hidden;
margin: 0;
}
p{ z-index: 99 !important; }
JavaScript
var container,
renderer,
scene,
camera,
mesh,
start = Date.now(),
fov = 30;
var clock = new THREE.Clock();
var timeUniform = {
iGlobalTime: {
type: 'f',
value: 0.1
},
iResolution: {
type: 'v2',
value: new THREE.Vector2()
}
};
timeUniform.iResolution.value.x = window.innerWidth;
timeUniform.iResolution.value.y = window.innerHeight;
window.addEventListener('load', function() {
container = document.getElementById('container');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
fov,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.x = 20;
camera.position.y = 10;
camera.position.z = 20;
camera.lookAt(scene.position);
scene.add(camera);
var axis = new THREE.AxisHelper(10);
scene.add (axis);
material = new THREE.ShaderMaterial({
uniforms: timeUniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var water = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(water);
var geometry = new THREE.SphereGeometry( 10, 32, 32 );
var material = new THREE.MeshBasicMaterial( {color: 0xffff00} );
var sphere = new THREE.Mesh( geometry, material );
scene.add( sphere );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
render();
});
window.addEventListener('resize',function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
function render() {
timeUniform.iGlobalTime.value += clock.getDelta();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
you can wrap ur animation in a div(your ThreeJs canvas). give the div and add this styles to it
position: absolute;
top: 0;
left: 0;
z-index: -1;
and make #container position: relative

THREE.JS | GLSL Set particle colour by scene texture

I have matrix of GLSL particles (THREE.Points), which have to be coloured by overall scene texture.
So, the result should be like this:
Yeah, I can create canvas and set buffer.color by ctx.getImageData(left, top, width, height), but is it possible to set it inside shader?
I have tried to get particle 'global' position, but without any success.
The output is:
var blob = "data:image/png;base64,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eG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMC0wNC0yOVQyMjozODowNCswMzowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjAtMDQtMjlUMjI6Mzg6MDQrMDM6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZWE1YjE0NGQtNjczOS00YmYyLTg0ZmYtNmFkMjlkYTZmNTlkIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6OGM0MWExZjUtMGE3NS1iNjRhLThlY2UtYTI0YjRlNjdlZmE3IiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGU0ODQwM2UtNTYzNS00MmM4LTkxYzQtZjZlODIzMzk0MDg5IiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9IkRpc3BsYXkiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjRlNDg0MDNlLTU2MzUtNDJjOC05MWM0LWY2ZTgyMzM5NDA4OSIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MjliNmNjMy01Y2Y3LTQzOWUtYTA1YS1lOTNhN2FjYzg2OWEiIHN0RXZ0OndoZW49IjIwMTktMDEtMjFUMTU6MzM6NDErMDM6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZWE1YjE0NGQtNjczOS00YmYyLTg0ZmYtNmFkMjlkYTZmNTlkIiBzdEV2dDp3aGVuPSIyMDIwLTA0LTI5VDIyOjM4OjA0KzAzOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+x4suaAAAA5FJREFUeJzt20uLHFUYxvHf1KgkKk4wMCEIUcGoMSAGyeBK8RLwsjFZ6CfwAuJ1bbI0++gmfgNduDOB6Ci6iYwKLtSxTSAmoHEGRm3BZBidlIszHXvavlR3ddWZrp4/NFRXV9V53rdP3d7LRJqmSuBa3Im7sBO3YBu2rP2+jD/wMy6ihh/xd9HCrinw2DvwKB7E/diDGzLu+xfm8TU+xywWCtBoooAZ8CyewZP++4fzsowTeB/vDemYGJ4DbsRLeAG3D+OAXTiH43gbl3IfLU3TvJ9X0zRdSMvn17Wxc+nPMwMewDHsz/0v5ONLvIIvBtk5GXDQN3FafOMJGk4Lmvqm3xlwHT7AU4MMVgIf4hBWsu7QjwN24lPhXr6RqeFh4XmiJ1kdcCvmMD24rlJZxAzO99owiwOm8T2259dVKku4R3BGR3o5YBJnFH9vL4pz2I3VThv0ugucNLrGE7Sf7LZBNwccxoGhyonDAcGWtnQ6Bfbi26IURWKvcC1bRycH1ITX1ypxRhub2p0Cz7fbsALsxoutK1tnwCTqsr+3jxqXMIV/GitaZ8Brqms8XC/YeJXWGbCEm0sUFIPfND3UNc+Ap1XfeIKNBxtfmh3wvwtEhXmusdA4BabwOyZiKSqZK8JMqDdmwOPGx3jCzH+isUAIX48bjxAcMCm8O48bM5icSNN0D74Rwl3jxAr2JULGZtyMJ9h8d4JdsZVEZFdidOJ8RTCdCFnacWVbgq2xVURk66CZoapwJcHl2CoispwIlRnjSj3RI3FQcRYTXIitIiIXEqEWJ3M2tUKs4IdEqMaajywmBvOoJULebC6ymBjMYbXxHDAbU0kkZtkMiV0NidVxKp6e0jkl2LwuKnw8jpYovNtY2EyMtPz4VrlaonC0+Uu75OifQg6tilzGTbokR1fxepmKSuYNTcYzXgUSZ4UagXV0CogcKlZLFA62W9nJAd/hSHFaSueIDjVPveoEP8JjRSgqkY91qXbLUih5FrcNV1Np/IQ75CiUXBX6ApaGp6k0lgTtHY0nW7/AgtD0NEqhs0VBc89Gq6xh8fO4T7g9bnRq2CdDpTj9dYxcxL1C99ZG5YSg8ZesO/SbGFkRukU61t5G5LCgra/4ZhWapr7Cy0pumrI24IxQeBjjArmwNvZ+AxrPaDdOviO02OZis3W2AAc02CFUYj0kX/P0Z/jECDVPt6Nd+/yU9e3zdRHa5/8FGLnMg5/e7DMAAAAASUVORK5CYII=";
var pattern = "data:image/jpeg;base64,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AABSZ3NNYm9vbAAAAAAAQ3JuQ2Jvb2wAAAAAAENudENib29sAAAAAABMYmxzYm9vbAAAAAAATmd0dmJvb2wAAAAAAEVtbERib29sAAAAAABJbnRyYm9vbAAAAAAAQmNrZ09iamMAAAABAAAAAAAAUkdCQwAAAAMAAAAAUmQgIGRvdWJAb+AAAAAAAAAAAABHcm4gZG91YkBv4AAAAAAAAAAAAEJsICBkb3ViQG/gAAAAAAAAAAAAQnJkVFVudEYjUmx0AAAAAAAAAAAAAAAAQmxkIFVudEYjUmx0AAAAAAAAAAAAAAAAUnNsdFVudEYjUHhsQFIAAAAAAAAAAAAKdmVjdG9yRGF0YWJvb2wBAAAAAFBnUHNlbnVtAAAAAFBnUHMAAAAAUGdQQwAAAABMZWZ0VW50RiNSbHQAAAAAAAAAAAAAAABUb3AgVW50RiNSbHQAAAAAAAAAAAAAAABTY2wgVW50RiNQcmNAWQAAAAAAAAAAABBjcm9wV2hlblByaW50aW5nYm9vbAAAAAAOY3JvcFJlY3RCb3R0b21sb25nAAAAAAAAAAxjcm9wUmVjdExlZnRsb25nAAAAAAAAAA1jcm9wUmVjdFJpZ2h0bG9uZwAAAAAAAAALY3JvcFJlY3RUb3Bsb25nAAAAAAA4QklNA+0AAAAAABAASAAAAAEAAgBIAAAAAQACOEJJTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNA/IAAAAAAAoAAP///////wAAOEJJTQQNAAAAAAAEAAAAWjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAjhCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQQeAAAAAAAEAAAAADhCSU0EGgAAAAADQwAAAAYAAAAAAAAAAAAAAIAAAACAAAAABwBwAGEAdAB0AGUAcgBuAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAgAAAAABSZ2h0bG9uZwAAAIAAAAAGc2xpY2VzVmxMcwAAAAFPYmpjAAAAAQAAAAAABXNsaWNlAAAAEgAAAAdzbGljZUlEbG9uZwAAAAAAAAAHZ3JvdXBJRGxvbmcAAAAAAAAABm9yaWdpbmVudW0AAAAMRVNsaWNlT3JpZ2luAAAADWF1dG9HZW5lcmF0ZWQAAAAAVHlwZWVudW0AAAAKRVNsaWNlVHlwZQAAAABJbWcgAAAABmJvdW5kc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAAAIAAAAAAUmdodGxvbmcAAACAAAAAA3VybFRFWFQAAAABAAAAAAAAbnVsbFRFWFQAAAABAAAAAAAATXNnZVRFWFQAAAABAAAAAAAGYWx0VGFnVEVYVAAAAAEAAAAAAA5jZWxsVGV4dElzSFRNTGJvb2wBAAAACGNlbGxUZXh0VEVYVAAAAAEAAAAAAAlob3J6QWxpZ25lbnVtAAAAD0VTbGljZUhvcnpBbGlnbgAAAAdkZWZhdWx0AAAACXZlcnRBbGlnbmVudW0AAAAPRVNsaWNlVmVydEFsaWduAAAAB2RlZmF1bHQAAAALYmdDb2xvclR5cGVlbnVtAAAAEUVTbGljZUJHQ29sb3JUeXBlAAAAAE5vbmUAAAAJdG9wT3V0c2V0bG9uZwAAAAAAAAAKbGVmdE91dHNldGxvbmcAAAAAAAAADGJvdHRvbU91dHNldGxvbmcAAAAAAAAAC3JpZ2h0T3V0c2V0bG9uZwAAAAAAOEJJTQQoAAAAAAAMAAAAAj/wAAAAAAAAOEJJTQQUAAAAAAAEAAAADThCSU0EDAAAAAAD6gAAAAEAAACAAAAAgAAAAYAAAMAAAAADzgAYAAH/2P/tAAxBZG9iZV9DTQAB/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAgACAAwEiAAIRAQMRAf/dAAQACP/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8AoJJJLtH0JSSSSSnsRwkkOEljPzcpJJJBTmJJJLzFuqTHhOmPCSnzE8lMnPJTL6Be1CkkkkUv/9Cgkkku0fQlJJJJKexHCSYcJ1ivzcpJJJJTmJJJLzFuqTHhOmPCSnzE8lMnPJTL6Be1Ukkkil//0fPkkkl6c01JxyEycchBD6cOE6YcJ18/PFKSSSSU6aSSS9OaSkx4TpHhJTxySSS2n6RUkkkkp//S8+SSSXpzTUnHITJxyEEPpw4TphwnXz88UpJJJJTppJJL05pKSPCSY8JKePSSSW0/SKkkkklP/9Ogkkku0fQlJJJJKewHCdIcJLFfm5SSSSSnMSSSXmLdUmPCdMeElPmJ5KZOeSmX0C9qpJJJFL//1KCSSS7R9CUkkkkp7EcJJhwnWK/NykkkklOYkkkvMW6pMeE6Y8JKfMTyUyc8lMvoF7VSSSSKX//V8+SSSXpzTUnHITJxyEEPpw4TphwnXz88UpJJJJTppJJL05pKTHhOkeElPHJJJLafpFSSSSSn/9bz5JJJenNNScchMnHIQQX04cJ0w4Tr5+eKUkkkkp00kkl6c0lJHhJI8IqeOSSSWy/SKkkkklP/2ThCSU0EIQAAAAAAUwAAAAEBAAAADwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAAABIAQQBkAG8AYgBlACAAUABoAG8AdABvAHMAaABvAHAAIABDAEMAAAABADhCSU0EBgAAAAAABwAHAQEAAQEA/+EO/2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDAgNzkuMTYwNDUxLCAyMDE3LzA1LzA2LTAxOjA4OjIxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIwLTA1LTAxVDEyOjIyOjA5KzAzOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTA1LTAxVDEyOjQ0OjI1KzAzOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMC0wNS0wMVQxMjo0NDoyNSswMzowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxMDE1YTk5Yy02MDFhLTQ4ZTYtYmZjYy1lZjFmZDQ1MDA3YTAiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo5NjU5MzU1MC1mNGVhLTAyNGUtOWM1YS0zMmNiYjNjMmQwZmYiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzM0N2QzZi1iZTA0LTQxNTQtODBlMS00ZWVhNDVmMTM0MDQiIGRjOmZvcm1hdD0iaW1hZ2UvanBlZyIgcGhvdG9zaG9wOkxlZ2FjeUlQVENEaWdlc3Q9IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iRGlzcGxheSBQMyI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDMzNDdkM2YtYmUwNC00MTU0LTgwZTEtNGVlYTQ1ZjEzNDA0IiBzdEV2dDp3aGVuPSIyMDIwLTA1LTAxVDEyOjIyOjA5KzAzOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjc3MDM5N2M0LTI3YTItNDM4ZS05MzZiLTAwMzA2MTA2YjAwMSIgc3RFdnQ6d2hlbj0iMjAyMC0wNS0wMVQxMjoyMjowOSswMzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMDE1YTk5Yy02MDFhLTQ4ZTYtYmZjYy1lZjFmZDQ1MDA3YTAiIHN0RXZ0OndoZW49IjIwMjAtMDUtMDFUMTI6NDQ6MjUrMDM6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/iAjRJQ0NfUFJPRklMRQABAQAAAiRhcHBsBAAAAG1udHJSR0IgWFlaIAfhAAcABwANABYAIGFjc3BBUFBMAAAAAEFQUEwAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbMoalYIlfxBNOJkT1dHqFYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmRlc2MAAAD8AAAAZWNwcnQAAAFkAAAAI3d0cHQAAAGIAAAAFHJYWVoAAAGcAAAAFGdYWVoAAAGwAAAAFGJYWVoAAAHEAAAAFHJUUkMAAAHYAAAAIGNoYWQAAAH4AAAALGJUUkMAAAHYAAAAIGdUUkMAAAHYAAAAIGRlc2MAAAAAAAAAC0Rpc3BsYXkgUDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxNwAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAg98AAD2/////u1hZWiAAAAAAAABKvwAAsTcAAAq5WFlaIAAAAAAAACg4AAARCwAAyLlwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQAAAKW3NmMzIAAAAAAAEMQgAABd7///MmAAAHkwAA/ZD///ui///9owAAA9wAAMBu/+4AIUFkb2JlAGRAAAAAAQMAEAMCAwYAAAAAAAAAAAAAAAD/2wCEAAEBAQEBAQEBAQECAQEBAgIBAQEBAgICAgICAgIDAgMDAwMCAwMEBAQEBAMFBQUFBQUHBwcHBwgICAgICAgICAgBAQEBAgICBAMDBAcFBAUHCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICP/CABEIAIAAgAMBEQACEQEDEQH/xACfAAEAAwEBAAAAAAAAAAAAAAAAAQMKAggBAQABBQEBAAAAAAAAAAAAAAADAQIECgsJCBAAAAQFBQEBAAAAAAAAAAAAAAECAxASMwQUETEyEzQgBREAAQICCAUFAQAAAAAAAAAAAQACMgMQQXGRInKSMyAhMbGyUWFCUhMjEgABAQcFAQEBAAAAAAAAAAABADFxkbECMnIQIEEiA1EhQv/aAAwDAQECEQMRAAAA8lbUm6sB3bh6RvLbhkWW4giCvhjQV+n+YainLlwi9oPaopypQNMflXv9gd24ekby24ZFluIIgr4Y0Ffp/mGopy5cIvaD2qKcqUDTH5V7/YHduHpG8tuGRZbiCIK+GNBX6f5hqKcuXCL2g9qinKlA0v8Albv6gWW4mkjyu4Z1lmIIir4a0H/p2IqijKlwkdnzawpypAABdix7t+MHqn34sQmWnuXfg+YploK78vNv6o9zGu7LAzQeqWgUBdixbuuL5qr3YkQ6mp7n36vmCZ6Cu7Lzc+pPc34uzAMznqpoCAXYsW7ri+aq92JEOpqe59+r5gmegruy83PqT3N+LswDNB6paBQF2LHu34weqffixCZae5d+D5imWgrvy82/qj3Ma7ssAAWW4mkjyu4Z1lmIIir4a0H/AKdiKooypcJHZ82sKcqQDS/5W7+oHduHpG8tuGRZbiCIK+GNBX6f5hqKcuXCL2g9qinKlA0x+Ve/2B3bh6RvLbhkWW4giCvhjQV+n+YainLlwi9oPaopypQNL/lbv6gWW4mkjyu4Z1lmIIir4a0H/p2IqijKlwkdnzawpypAABdix7t+MHqn34sQmWnuXfg+YploK78vNv6o9zGu7LAzQeqWgUBdixbuuL5qr3YkQ6mp7n36vmCZ6Cu7Lzc+pPc34uzAMznqpoCAXYsW7ri+aq92JEOpqe59+r5gmegruy83PqT3N+LswDM56qaAgF2LFu64vmqvdiRDqanuffq+YJnoK7svNz6k9zfi7MA//9oACAECAAEFAIr2XvC5p3FSFjXsaH0vZe8LmncVIWNexofS9l7wuadxUhY17GhGQxIYkMLQcq7lvXJbGS2Li4bNt9hw3MdwY7gsrdwnrFZdE5CchOQkISEJCF8gui9uHCeyHBkOBh9w3Le3bNvGbGM2EWzeqFnLOYnMTn8X1C+rwt6ltTgjdG31fUL6vC3qW1OCN0bRnITkJyF8sui9t3Dex3BjuBhhwnLe4bJvJbGS2EXLeqEHLIYkMSGJzE5icwtZyrtm9cZsYzYuLdsm333CcyHBkOCyuHDesUF0SEJCEhfC9l7wuadxUhY17Gh9L2XvC5p3FSFjXsaEZDEhiQwtByruW9clsZLYuLhs232HDcx3BjuCyt3CesVl0TkJyE5CQhIQkIXyC6L24cJ7IcGQ4GH3Dct7ds28ZsYzYRbN6oWcs5icxOfxfUL6vC3qW1OCN0bfV9Qvq8LepbU4I3Rt9X1C+rwt6ltTgjdG0f/aAAgBAwABBQCP6Pn/AEPRAty2gzzZ4fX6Pn/Q9EC3LaDPNnh9fo+f9D0QLctoM82eEclsZLYyWxf3DZsfoEeRoY0MER6kZaakNSDJlOzbOSYzgxnBjODJcGS4MlwPXLkjxFPoQ0IGRaGZ66mNTH55nkWFu2bGM2MZsYzfw9we5wPY94fn+j87z/T3B7nA9j3h+f6PzvPHGcGM4MZwPWzkjxlPqQ1IGZaGR66GNDH55HkWFw2TGS2MlsZLYxmxjNjGbF/btkx+gZ5GpjUwRnqRFpoQ0IMkU7Ny5JkuDJcGS58fo+f9D0QLctoM82eH1+j5/wBD0QLctoM82eEclsZLYyWxf3DZsfoEeRoY0MER6kZaakNSDJlOzbOSYzgxnBjODJcGS4MlwPXLkjxFPoQ0IGRaGZ66mNTH55nkWFu2bGM2MZsYzfw9we5wPY94fn+j87z/AE9we5wPY94fn+j87z/T3B7nA9j3h+f6PzvPH//aAAgBAQABBQCLVVmlBXFXKFz57n0fTVVmlBXFXKFz57n0fTVVmlBXFXKFz57n0R7Wh2tDtaDLrRvMqT1TJEyQpSZVJVNKoSqFylWPctO5HU6Op0dTo7XR2ujtdFs67kWylY8yhMoJUqZKUyypEqQ8lPU800b3U0OpodTXxbei288E8k8YPUnav1bei288E8k8YPUnasep0dTo6nRbNO5FslWPKoSqCUqmSpMsyRMkPKT1POtE92tDtaHa0OpodTQ6mgy00TzKU9UqRKkKSmVSlTTKEyhcqVj3LruR2ujtdHa78NVWaUFcVcoXPnufR9NVWaUFcVcoXPnufRHtaHa0O1oMutG8ypPVMkTJClJlUlU0qhKoXKVY9y07kdTo6nR1OjtdHa6O10WzruRbKVjzKEyglSpkpTLKkSpDyU9TzTRvdTQ6mh1NfFt6LbzwTyTxg9Sdq/Vt6LbzwTyTxg9Sdq/Vt6LbzwTyTxg9Sdqx/9oACAECAgY/ANSjrU4qp5nrRkJqjES3lHWpxVTzPWjITVGIlvKOtTiqnmetGQmqMRLYxMTEfxHsIq4RVwiquwYeQqupaeCrTAq0wKo6lo4P1Ufv8iSampqYmJir/P5MlX2LTyfquMSrjEqnsWjkqnqGDgK0QVogh1EEP1NTU3ZXiZKvIz1peJqlw1CG+vEyVeRnrS8TVLhqENjU1NVf7/Jkq+paeD9VpgVaYFU9S0cFU9gwchXCKuEUOwih+JiYmJqamo/qPUQVogrRBVdQw8BVdi08lXGJVxiVR2LRyfqo/P5EkxMTNhR1qcVU8z1oyE1RiJbyjrU4qp5nrRkJqjES2MTExH8R7CKuEVcIqrsGHkKrqWngq0wKtMCqOpaOD9VH7/IkmpqamJiYq/z+TJV9i08n6rjEq4xKp7Fo5Kp6hg4CtEFaIIdRBD9TU1N2V4mSryM9aXiapcNQhvrxMlXkZ60vE1S4ahDfXiZKvIz1peJqlw1CGz//2gAIAQMCBj8A19MTJemRnvD0Hb/TEyXpkZ7w9B2/0xMl6ZGe8PQdsuEVcIq4RVfYWnkfF6ZGe8PQ6lnxWmCtMFaYK4xVxirjFHsWfUX7/PITVHUWjgfFaIK0QVohsLkX7/PITXniJby5F+/zyE154iWy0wVpgrTBHqWfEX7/ADyE1R2Fo5HxXCKuEVcIq0QVogrRBV9RaeB8XpkZ7w9DsWfVcYq4xVxjs9MTJemRnvD0Hb/TEyXpkZ7w9B2y4RVwirhFV9haeR8XpkZ7w9DqWfFaYK0wVpgrjFXGKuMUexZ9Rfv88hNUdRaOB8VogrRBWiGwuRfv88hNeeIlvLkX7/PITXniJby5F+/zyE154iWz/9oACAEBAQY/AKZeYd1LyjtS6wp1ppn5HeJU/O7yPHLzDupeUdqXWFOtNM/I7xKn53eR45eYd1LyjtS6wp1ppn5HeJU/O7yPBut1BbrdQW63UFKH6t5uaObh6qXiEIr9lEL1EL07EOhrCdhPU1FQm5Qm5T8JgdUfqVP/AJOjd8T9itp2kradpK2naSt12orddqK3XaipH9XRt+R+wUjEYG1n6hRG9RG9NxHqKym4R0FQUIuUIuUzCITV7Kafybzc48mj1W03SFtN0hbTdI4JGdvkFIyN8RS20JtgpmZT2UzMe/HIzt8gpGRviKW2hNsFMzKeymZj34Np2kradpK2naSpH8nRt+J+wUjCYG1H6hQm5Qm5NwnqKim4h0FYUQvUQvUzEITX7KaP1byc4cnD1W63UFut1BbrdQW03SFtN0hbTdIUo/k3k5p5tHqpeEQir2UIuUIuTsI6GoJ2I9TWVEb1Eb1PxGB1Z+pU/wDq6N3yP2K3Xait12orddqPBLzDupeUdqXWFOtNM/I7xKn53eR45eYd1LyjtS6wp1ppn5HeJU/O7yPBut1BbrdQW63UFKH6t5uaObh6qXiEIr9lEL1EL07EOhrCdhPU1FQm5Qm5T8JgdUfqVP8A5Ojd8T9itp2kradpK2naSt12orddqK3XaipH9XRt+R+wUjEYG1n6hRG9RG9NxHqKym4R0FQUIuUIuUzCITV7Kafybzc48mj1W03SFtN0hbTdI4JGdvkFIyN8RS20JtgpmZT2UzMe/HIzt8gpGRviKW2hNsFMzKeymZj345GdvkFIyN8RS20JtgpmZT2UzMe/B//Z";
var width = 512,
height = 512;
inits();
function inits(){
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
scene.add(camera);
var N = 8.0;
var points = [];
var colors = [];
for(var y = 16; y <= height - 16; y += N){
for(var x = 16; x <= width - 16; x += N){
var dx = -1.0 + x / width * 2;
var dy = -1.0 + y / height * 2;
points.push(new THREE.Vector2(dx, dy));
}
}
buffer = {
position: new Float32Array(points.length * 3),
color: new Float32Array(points.length * 3),
radius: new Float32Array(points.length * 1),
};
for(var i = 0; i < points.length; i++){
buffer.position[i * 3] = points[i].x;
buffer.position[i * 3 + 1] = points[i].y;
buffer.position[i * 3 + 2] = 0;
buffer.radius[i] = N * 0.5;
buffer.color[i * 3] = 1.0;
buffer.color[i * 3 + 1] = 0.0;
buffer.color[i * 3 + 2] = 1.0;
}
uniforms = {
blob: { value: new THREE.TextureLoader().load(blob) },
pattern: { value: new THREE.TextureLoader().load(pattern) }
};
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
transparent: true
} );
material.extensions.fragDepth = true;
material.extensions.drawBuffers = true;
var cloud = new THREE.BufferGeometry();
cloud.addAttribute('position', new THREE.BufferAttribute(buffer.position, 3));
cloud.addAttribute('radius', new THREE.BufferAttribute(buffer.radius, 1));
cloud.addAttribute('color', new THREE.BufferAttribute(buffer.color, 3));
var points = new THREE.Points(cloud, material);
points.dynamic = true;
points.frustumCulled = false;
scene.add(points);
animate();
}
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PixelWordMap Demo</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="author" content="Vladimir V. KUCHINOV">
<link rel="stylesheet" href="styles.css" charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.js"></script>
</head>
<body>
<script type='x-shader/x-vertex' id='vertexShader'>
uniform sampler2D blob;
uniform sampler2D pattern;
attribute float radius;
attribute vec3 color;
varying vec3 vColor;
varying vec2 pos;
void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = radius;
pos = vec2(1.0 + position.x / 2.0, 1.0 + position.y / 2.0);
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type='x-shader/x-fragment' id='fragmentShader'>
uniform sampler2D blob;
uniform sampler2D pattern;
varying vec3 vColor;
varying vec2 pos;
void main() {
vec4 color = texture2D(pattern, vec2(pos.x, pos.y));
gl_FragColor = color * texture2D(blob, gl_PointCoord);
if(gl_FragColor.a < 0.1) discard;
}
</script>
</body>
</html>
The vertex shader output varying vec2 pos; is never assigned a value, because the position is assigned to the local variable pos with the same name: vec2 pos = vec2(...);.
Furthermore you missed the braces, to prioritized the + operation:
pos = (position.xy + 1.0) / 2.0;
Choose a different number for N to get a different number of points:
var N = 32;
for(var y = N/2; y <= height; y += N){
for(var x = N/2; x <= width; x += N){
var dx = -1.0 + x / width * 2;
var dy = -1.0 + y / height * 2;
points.push(new THREE.Vector2(dx, dy));
}
}
Note, the point size is limited. See Working around gl_PointSize limitations in three.js / webGL.
var blob = "data:image/png;base64,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eG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMC0wNC0yOVQyMjozODowNCswMzowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjAtMDQtMjlUMjI6Mzg6MDQrMDM6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ZWE1YjE0NGQtNjczOS00YmYyLTg0ZmYtNmFkMjlkYTZmNTlkIiB4bXBNTTpEb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6OGM0MWExZjUtMGE3NS1iNjRhLThlY2UtYTI0YjRlNjdlZmE3IiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NGU0ODQwM2UtNTYzNS00MmM4LTkxYzQtZjZlODIzMzk0MDg5IiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9IkRpc3BsYXkiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjRlNDg0MDNlLTU2MzUtNDJjOC05MWM0LWY2ZTgyMzM5NDA4OSIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0yMVQxNTozMzo0MSswMzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo3MjliNmNjMy01Y2Y3LTQzOWUtYTA1YS1lOTNhN2FjYzg2OWEiIHN0RXZ0OndoZW49IjIwMTktMDEtMjFUMTU6MzM6NDErMDM6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6ZWE1YjE0NGQtNjczOS00YmYyLTg0ZmYtNmFkMjlkYTZmNTlkIiBzdEV2dDp3aGVuPSIyMDIwLTA0LTI5VDIyOjM4OjA0KzAzOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+x4suaAAAA5FJREFUeJzt20uLHFUYxvHf1KgkKk4wMCEIUcGoMSAGyeBK8RLwsjFZ6CfwAuJ1bbI0++gmfgNduDOB6Ci6iYwKLtSxTSAmoHEGRm3BZBidlIszHXvavlR3ddWZrp4/NFRXV9V53rdP3d7LRJqmSuBa3Im7sBO3YBu2rP2+jD/wMy6ihh/xd9HCrinw2DvwKB7E/diDGzLu+xfm8TU+xywWCtBoooAZ8CyewZP++4fzsowTeB/vDemYGJ4DbsRLeAG3D+OAXTiH43gbl3IfLU3TvJ9X0zRdSMvn17Wxc+nPMwMewDHsz/0v5ONLvIIvBtk5GXDQN3FafOMJGk4Lmvqm3xlwHT7AU4MMVgIf4hBWsu7QjwN24lPhXr6RqeFh4XmiJ1kdcCvmMD24rlJZxAzO99owiwOm8T2259dVKku4R3BGR3o5YBJnFH9vL4pz2I3VThv0ugucNLrGE7Sf7LZBNwccxoGhyonDAcGWtnQ6Bfbi26IURWKvcC1bRycH1ITX1ypxRhub2p0Cz7fbsALsxoutK1tnwCTqsr+3jxqXMIV/GitaZ8Brqms8XC/YeJXWGbCEm0sUFIPfND3UNc+Ap1XfeIKNBxtfmh3wvwtEhXmusdA4BabwOyZiKSqZK8JMqDdmwOPGx3jCzH+isUAIX48bjxAcMCm8O48bM5icSNN0D74Rwl3jxAr2JULGZtyMJ9h8d4JdsZVEZFdidOJ8RTCdCFnacWVbgq2xVURk66CZoapwJcHl2CoispwIlRnjSj3RI3FQcRYTXIitIiIXEqEWJ3M2tUKs4IdEqMaajywmBvOoJULebC6ymBjMYbXxHDAbU0kkZtkMiV0NidVxKp6e0jkl2LwuKnw8jpYovNtY2EyMtPz4VrlaonC0+Uu75OifQg6tilzGTbokR1fxepmKSuYNTcYzXgUSZ4UagXV0CogcKlZLFA62W9nJAd/hSHFaSueIDjVPveoEP8JjRSgqkY91qXbLUih5FrcNV1Np/IQ75CiUXBX6ApaGp6k0lgTtHY0nW7/AgtD0NEqhs0VBc89Gq6xh8fO4T7g9bnRq2CdDpTj9dYxcxL1C99ZG5YSg8ZesO/SbGFkRukU61t5G5LCgra/4ZhWapr7Cy0pumrI24IxQeBjjArmwNvZ+AxrPaDdOviO02OZis3W2AAc02CFUYj0kX/P0Z/jECDVPt6Nd+/yU9e3zdRHa5/8FGLnMg5/e7DMAAAAASUVORK5CYII=";
var pattern = "data:image/jpeg;base64,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AABSZ3NNYm9vbAAAAAAAQ3JuQ2Jvb2wAAAAAAENudENib29sAAAAAABMYmxzYm9vbAAAAAAATmd0dmJvb2wAAAAAAEVtbERib29sAAAAAABJbnRyYm9vbAAAAAAAQmNrZ09iamMAAAABAAAAAAAAUkdCQwAAAAMAAAAAUmQgIGRvdWJAb+AAAAAAAAAAAABHcm4gZG91YkBv4AAAAAAAAAAAAEJsICBkb3ViQG/gAAAAAAAAAAAAQnJkVFVudEYjUmx0AAAAAAAAAAAAAAAAQmxkIFVudEYjUmx0AAAAAAAAAAAAAAAAUnNsdFVudEYjUHhsQFIAAAAAAAAAAAAKdmVjdG9yRGF0YWJvb2wBAAAAAFBnUHNlbnVtAAAAAFBnUHMAAAAAUGdQQwAAAABMZWZ0VW50RiNSbHQAAAAAAAAAAAAAAABUb3AgVW50RiNSbHQAAAAAAAAAAAAAAABTY2wgVW50RiNQcmNAWQAAAAAAAAAAABBjcm9wV2hlblByaW50aW5nYm9vbAAAAAAOY3JvcFJlY3RCb3R0b21sb25nAAAAAAAAAAxjcm9wUmVjdExlZnRsb25nAAAAAAAAAA1jcm9wUmVjdFJpZ2h0bG9uZwAAAAAAAAALY3JvcFJlY3RUb3Bsb25nAAAAAAA4QklNA+0AAAAAABAASAAAAAEAAgBIAAAAAQACOEJJTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNA/IAAAAAAAoAAP///////wAAOEJJTQQNAAAAAAAEAAAAWjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAjhCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQQeAAAAAAAEAAAAADhCSU0EGgAAAAADQwAAAAYAAAAAAAAAAAAAAIAAAACAAAAABwBwAGEAdAB0AGUAcgBuAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAgAAAAABSZ2h0bG9uZwAAAIAAAAAGc2xpY2VzVmxMcwAAAAFPYmpjAAAAAQAAAAAABXNsaWNlAAAAEgAAAAdzbGljZUlEbG9uZwAAAAAAAAAHZ3JvdXBJRGxvbmcAAAAAAAAABm9yaWdpbmVudW0AAAAMRVNsaWNlT3JpZ2luAAAADWF1dG9HZW5lcmF0ZWQAAAAAVHlwZWVudW0AAAAKRVNsaWNlVHlwZQAAAABJbWcgAAAABmJvdW5kc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAAAIAAAAAAUmdodGxvbmcAAACAAAAAA3VybFRFWFQAAAABAAAAAAAAbnVsbFRFWFQAAAABAAAAAAAATXNnZVRFWFQAAAABAAAAAAAGYWx0VGFnVEVYVAAAAAEAAAAAAA5jZWxsVGV4dElzSFRNTGJvb2wBAAAACGNlbGxUZXh0VEVYVAAAAAEAAAAAAAlob3J6QWxpZ25lbnVtAAAAD0VTbGljZUhvcnpBbGlnbgAAAAdkZWZhdWx0AAAACXZlcnRBbGlnbmVudW0AAAAPRVNsaWNlVmVydEFsaWduAAAAB2RlZmF1bHQAAAALYmdDb2xvclR5cGVlbnVtAAAAEUVTbGljZUJHQ29sb3JUeXBlAAAAAE5vbmUAAAAJdG9wT3V0c2V0bG9uZwAAAAAAAAAKbGVmdE91dHNldGxvbmcAAAAAAAAADGJvdHRvbU91dHNldGxvbmcAAAAAAAAAC3JpZ2h0T3V0c2V0bG9uZwAAAAAAOEJJTQQoAAAAAAAMAAAAAj/wAAAAAAAAOEJJTQQUAAAAAAAEAAAADThCSU0EDAAAAAAD6gAAAAEAAACAAAAAgAAAAYAAAMAAAAADzgAYAAH/2P/tAAxBZG9iZV9DTQAB/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAgACAAwEiAAIRAQMRAf/dAAQACP/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8AoJJJLtH0JSSSSSnsRwkkOEljPzcpJJJBTmJJJLzFuqTHhOmPCSnzE8lMnPJTL6Be1CkkkkUv/9Cgkkku0fQlJJJJKexHCSYcJ1ivzcpJJJJTmJJJLzFuqTHhOmPCSnzE8lMnPJTL6Be1Ukkkil//0fPkkkl6c01JxyEycchBD6cOE6YcJ18/PFKSSSSU6aSSS9OaSkx4TpHhJTxySSS2n6RUkkkkp//S8+SSSXpzTUnHITJxyEEPpw4TphwnXz88UpJJJJTppJJL05pKSPCSY8JKePSSSW0/SKkkkklP/9Ogkkku0fQlJJJJKewHCdIcJLFfm5SSSSSnMSSSXmLdUmPCdMeElPmJ5KZOeSmX0C9qpJJJFL//1KCSSS7R9CUkkkkp7EcJJhwnWK/NykkkklOYkkkvMW6pMeE6Y8JKfMTyUyc8lMvoF7VSSSSKX//V8+SSSXpzTUnHITJxyEEPpw4TphwnXz88UpJJJJTppJJL05pKTHhOkeElPHJJJLafpFSSSSSn/9bz5JJJenNNScchMnHIQQX04cJ0w4Tr5+eKUkkkkp00kkl6c0lJHhJI8IqeOSSSWy/SKkkkklP/2ThCSU0EIQAAAAAAUwAAAAEBAAAADwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAAABIAQQBkAG8AYgBlACAAUABoAG8AdABvAHMAaABvAHAAIABDAEMAAAABADhCSU0EBgAAAAAABwAHAQEAAQEA/+EO/2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDAgNzkuMTYwNDUxLCAyMDE3LzA1LzA2LTAxOjA4OjIxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiIHhtcDpDcmVhdGVEYXRlPSIyMDIwLTA1LTAxVDEyOjIyOjA5KzAzOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTA1LTAxVDEyOjQ0OjI1KzAzOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMC0wNS0wMVQxMjo0NDoyNSswMzowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxMDE1YTk5Yy02MDFhLTQ4ZTYtYmZjYy1lZjFmZDQ1MDA3YTAiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo5NjU5MzU1MC1mNGVhLTAyNGUtOWM1YS0zMmNiYjNjMmQwZmYiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzM0N2QzZi1iZTA0LTQxNTQtODBlMS00ZWVhNDVmMTM0MDQiIGRjOmZvcm1hdD0iaW1hZ2UvanBlZyIgcGhvdG9zaG9wOkxlZ2FjeUlQVENEaWdlc3Q9IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0iRGlzcGxheSBQMyI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MDMzNDdkM2YtYmUwNC00MTU0LTgwZTEtNGVlYTQ1ZjEzNDA0IiBzdEV2dDp3aGVuPSIyMDIwLTA1LTAxVDEyOjIyOjA5KzAzOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgKE1hY2ludG9zaCkiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjc3MDM5N2M0LTI3YTItNDM4ZS05MzZiLTAwMzA2MTA2YjAwMSIgc3RFdnQ6d2hlbj0iMjAyMC0wNS0wMVQxMjoyMjowOSswMzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMDE1YTk5Yy02MDFhLTQ4ZTYtYmZjYy1lZjFmZDQ1MDA3YTAiIHN0RXZ0OndoZW49IjIwMjAtMDUtMDFUMTI6NDQ6MjUrMDM6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/iAjRJQ0NfUFJPRklMRQABAQAAAiRhcHBsBAAAAG1udHJSR0IgWFlaIAfhAAcABwANABYAIGFjc3BBUFBMAAAAAEFQUEwAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbMoalYIlfxBNOJkT1dHqFYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmRlc2MAAAD8AAAAZWNwcnQAAAFkAAAAI3d0cHQAAAGIAAAAFHJYWVoAAAGcAAAAFGdYWVoAAAGwAAAAFGJYWVoAAAHEAAAAFHJUUkMAAAHYAAAAIGNoYWQAAAH4AAAALGJUUkMAAAHYAAAAIGdUUkMAAAHYAAAAIGRlc2MAAAAAAAAAC0Rpc3BsYXkgUDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxNwAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAg98A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";
var width = 512,
height = 512;
inits();
function inits(){
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
scene.add(camera);
var points = [];
var colors = [];
var N = 32;
for(var y = N/2; y <= height; y += N){
for(var x = N/2; x <= width; x += N){
var dx = -1.0 + x / width * 2;
var dy = -1.0 + y / height * 2;
points.push(new THREE.Vector2(dx, dy));
}
}
buffer = {
position: new Float32Array(points.length * 3),
color: new Float32Array(points.length * 3),
radius: new Float32Array(points.length * 1),
};
for(var i = 0; i < points.length; i++){
buffer.position[i * 3] = points[i].x;
buffer.position[i * 3 + 1] = points[i].y;
buffer.position[i * 3 + 2] = 0;
buffer.radius[i] = N * 0.5;
buffer.color[i * 3] = 1.0;
buffer.color[i * 3 + 1] = 0.0;
buffer.color[i * 3 + 2] = 1.0;
}
uniforms = {
blob: { value: new THREE.TextureLoader().load(blob) },
pattern: { value: new THREE.TextureLoader().load(pattern) }
};
var material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
transparent: true
} );
material.extensions.fragDepth = true;
material.extensions.drawBuffers = true;
var cloud = new THREE.BufferGeometry();
cloud.addAttribute('position', new THREE.BufferAttribute(buffer.position, 3));
cloud.addAttribute('radius', new THREE.BufferAttribute(buffer.radius, 1));
cloud.addAttribute('color', new THREE.BufferAttribute(buffer.color, 3));
var points = new THREE.Points(cloud, material);
points.dynamic = true;
points.frustumCulled = false;
scene.add(points);
animate();
}
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
<script src="https://cdn.jsdelivr.net/npm/three#0.115/build/three.js"></script>
<script type='x-shader/x-vertex' id='vertexShader'>
uniform sampler2D blob;
uniform sampler2D pattern;
attribute float radius;
attribute vec3 color;
varying vec3 vColor;
varying vec2 pos;
void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = radius;
pos = (position.xy + 1.0) / 2.0;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type='x-shader/x-fragment' id='fragmentShader'>
uniform sampler2D blob;
uniform sampler2D pattern;
varying vec3 vColor;
varying vec2 pos;
void main() {
vec4 color = texture2D(pattern, vec2(pos.x, pos.y));
gl_FragColor = color * texture2D(blob, gl_PointCoord);
if(gl_FragColor.a < 0.1) discard;
}
</script>

how to import a height map in WebGL

I know that in theory you have to first find the coordinates on the height map like (x = width HM / width Terrain * x Terrain) and y coordinate (y = height HM / height Terrain * y Terrain) and after getting the location on the height map, we get the actual height by min_height + (colorVal / (max_color - min_color) * *max_height - min_height) thus returning a Z value for a particular segment.
But how can i actually import the height map and take its parameters? I'm writing in javascript with no additional libraries (three,babylon).
edit
Currently i'm hardcoding the z values based on x and y ranges:
Plane.prototype.modifyGeometry=function(x,y){
if((x>=0&&x<100)&&(y>=0&&y<100)){
return 25;
}
else if((x>=100&&x<150)&&(y>=100&&y<150)){
return 20;
}
else if((x>=150&&x<200)&&(y>=150&&y<200)){
return 15;
}
else if((x>=200&&x<250)&&(y>=200&&y<250)){
return 10;
}
else if((x>=250&&x<300)&&(y>=250&&y<300)){
return 5;
}
else{
return 0;
}
** edit **
i can get a flat grid (or with randomly generated heights), but as soon as i add the image data, i get a blank screen(no errors though). Here is the code (i changed it up a bit):
var gl;
var canvas;
var img = new Image();
// img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
var gridWidth;
var gridDepth;
var gridPoints = [];
var gridIndices = [];
var rowOff = 0;
var rowStride = gridWidth + 1;
var numVertices = (gridWidth * 2 * (gridDepth + 1)) + (gridDepth * 2 * (gridWidth + 1));
//creating plane
function generateHeightPoints() {
var ctx = document.createElement("canvas").getContext("2d"); //using 2d canvas to read image
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
gridWidth = imgData.width - 1;
gridDepth = imgData.height - 1;
for (var z = 0; z <= gridDepth; ++z) {
for (var x = 0; x <= gridWidth; ++x) {
var offset = (z * imgData.width + x) * 4;
var height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
}
function generateIndices() {
for (var z = 0; z<=gridDepth; ++z) {
rowOff = z*rowStride;
for(var x = 0; x<gridWidth; ++x) {
gridIndices.push(rowOff+x,rowOff+x+1);
}
}
for (var x = 0; x<=gridWidth; ++x) {
for(var z = 0; z<gridDepth; ++z) {
rowOff = z * rowStride;
gridIndices.push(rowOff+x,rowOff+x+rowStride);
}
}
}
//init
//program init
window.onload = function init()
{
canvas = document.getElementById( "gl-canvas" );
gl = WebGLUtils.setupWebGL( canvas );
if ( !gl ) { alert( "WebGL isn't available" ); }
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var program = initShaders( gl, "vertex-shader", "fragment-shader" );
gl.useProgram( program );
generateHeightPoints();
generateIndices();
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints),
gl.STATIC_DRAW);
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices),
gl.STATIC_DRAW);
var vPosition = gl.getAttribLocation( program, "vPosition" );
gl.vertexAttribPointer( vPosition, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vPosition );
var matrixLoc = gl.getUniformLocation(program, 'matrix');
var m4 = twgl.m4;
var projection = m4.perspective(60 * Math.PI / 180, gl.canvas.clientWidth /
gl.canvas.clientHeight, 0.1, 100);
var cameraPosition = [-18, 15, -10];
var target = [gridWidth / 2, -10, gridDepth / 2];
var up = [0, 1, 0];
var camera = m4.lookAt(cameraPosition, target, up);
var view = m4.inverse(camera);
var mat = m4.multiply(projection, view);
gl.uniformMatrix4fv(matrixLoc, false, mat);
render();
}
function render() {
gl.drawElements(gl.LINES, numVertices, gl.UNSIGNED_SHORT, 0);
gl.drawElements(gl.LINES,numVertices,gl.UNSIGNED_SHORT,0);
requestAnimFrame(render);
}
You basically just make a grid of points and change the Z values.
First a flat grid
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
const gridWidth = 40;
const gridDepth = 40;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
gridPoints.push(x, 0, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-gridWidth / 8, 10, -gridDepth / 8];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Grid with height map.
Here's a gray scale image we can use as a height map
Read it by loading a img, drawing to a 2D canvas, calling getImageData. Then just read the red values for height.
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision highp float;
void main() {
gl_FragColor = vec4(0, 1, 0, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const matrixLoc = gl.getUniformLocation(program, 'matrix');
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const gridWidth = imgData.width - 1;
const gridDepth = imgData.height - 1;
const gridPoints = [];
for (let z = 0; z <= gridDepth; ++z) {
for (let x = 0; x <= gridWidth; ++x) {
const offset = (z * imgData.width + x) * 4;
// height 0 to 10
const height = imgData.data[offset] * 10 / 255;
gridPoints.push(x, height, z);
}
}
const gridIndices = [];
const rowStride = gridWidth + 1;
// x lines
for (let z = 0; z <= gridDepth; ++z) {
const rowOff = z * rowStride;
for (let x = 0; x < gridWidth; ++x) {
gridIndices.push(rowOff + x, rowOff + x + 1);
}
}
// z lines
for (let x = 0; x <= gridWidth; ++x) {
for (let z = 0; z < gridDepth; ++z) {
const rowOff = z * rowStride;
gridIndices.push(rowOff + x, rowOff + x + rowStride);
}
}
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(gridPoints), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(gridIndices), gl.STATIC_DRAW);
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const m4 = twgl.m4;
const projection = m4.perspective(
60 * Math.PI / 180, // field of view
gl.canvas.clientWidth / gl.canvas.clientHeight, // aspect
0.1, // near
100, // far
);
const cameraPosition = [-10, 10, -10];
const target = [gridWidth / 2, -10, gridDepth / 2];
const up = [0, 1, 0];
const camera = m4.lookAt(cameraPosition, target, up);
const view = m4.inverse(camera);
const mat = m4.multiply(projection, view);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(
positionLoc, // location
3, // size
gl.FLOAT, // type
false, // normalize
0, // stride
0, // offset
);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLoc, false, mat);
const numVertices = (gridWidth * 2 * (gridDepth + 1)) +
(gridDepth * 2 * (gridWidth + 1));
gl.drawElements(
gl.LINES, // primitive type
numVertices, //
gl.UNSIGNED_SHORT, // type of indices
0, // offset
);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Then instead of making a grid of lines make a grid of triangles. There's lots of ways to do that. You could put 2 triangle per grid square. This code put's 4. You also need to generate normals. I copied the code to generate normals from this article which is fairly generic normal generating code. Being a grid you could make a grid specific normal generator which would be faster since being a grid you know which vertices are shared.
This code is also using twgl because WebGL is too verbose but it should be clear how to do it in plain WebGL from reading the names of the twgl functions.
'use strict';
/* global twgl, m4, requestAnimationFrame, document */
const img = new Image();
img.onload = run;
img.crossOrigin = 'anonymous';
img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
function run() {
// use a canvas 2D to read the image
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
function getHeight(offset) {
const v = imgData.data[offset * 4]; // x4 because RGBA
return v * 10 / 255; // 0 to 10
}
// first generate a grid of triangles, at least 2 or 4 for each cell
//
// 2 4
// +----+ +----+
// | /| |\ /|
// | / | | \/ |
// | / | | /\ |
// |/ | |/ \|
// +----+ +----+
//
const positions = [];
const texcoords = [];
const indices = [];
const cellsAcross = imgData.width - 1;
const cellsDeep = imgData.height - 1;
for (let z = 0; z < cellsDeep; ++z) {
for (let x = 0; x < cellsAcross; ++x) {
const base0 = z * imgData.width + x;
const base1 = base0 + imgData.width;
const h00 = getHeight(base0); const h01 = getHeight(base0 + 1);
const h10 = getHeight(base1);
const h11 = getHeight(base1 + 1);
const hm = (h00 + h01 + h10 + h11) / 4;
const x0 = x;
const x1 = x + 1;
const z0 = z;
const z1 = z + 1;
const ndx = positions.length / 3;
positions.push(
x0, h00, z0,
x1, h01, z0,
x0, h10, z1,
x1, h11, z1,
(x0 + x1) / 2, hm, (z0 + z1) / 2,
);
const u0 = x / cellsAcross;
const v0 = z / cellsDeep;
const u1 = (x + 1) / cellsAcross;
const v1 = (z + 1) / cellsDeep;
texcoords.push(
u0, v0,
u1, v0,
u0, v1,
u1, v1,
(u0 + u1) / 2, (v0 + v1) / 2,
);
//
// 0----1
// |\ /|
// | \/4|
// | /\ |
// |/ \|
// 2----3
indices.push(
ndx, ndx + 4, ndx + 1,
ndx, ndx + 2, ndx + 4,
ndx + 2, ndx + 3, ndx + 4,
ndx + 1, ndx + 4, ndx + 3,
);
}
}
const maxAngle = 2 * Math.PI / 180; // make them facetted
const arrays = generateNormals({
position: positions,
texcoord: texcoords,
indices,
}, maxAngle);
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
uniform mat4 projection;
uniform mat4 modelView;
varying vec3 v_normal;
varying vec2 v_texcoord;
void main() {
gl_Position = projection * modelView * position;
v_normal = mat3(modelView) * normal;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec3 v_normal;
varying vec2 v_texcoord;
varying float v_modelId;
void main() {
vec3 lightDirection = normalize(vec3(1, 2, -3)); // arbitrary light direction
float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
gl_FragColor = vec4(vec3(0,1,0) * l, 1);
}
`;
// compile shader, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make some vertex data
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
time *= 0.001; // seconds
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
const fov = Math.PI * 0.25;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const near = 0.1;
const far = 100;
const projection = m4.perspective(fov, aspect, near, far);
const eye = [0, 10, 25];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
let modelView = m4.yRotate(view, time);
modelView = m4.translate(modelView, imgData.width / -2, 0, imgData.height / -2)
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniforms(programInfo, {
projection,
modelView,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function generateNormals(arrays, maxAngle) {
const positions = arrays.position;
const texcoords = arrays.texcoord;
// first compute the normal of each face
let getNextIndex = makeIndiceIterator(arrays);
const numFaceVerts = getNextIndex.numElements;
const numVerts = arrays.position.length;
const numFaces = numFaceVerts / 3;
const faceNormals = [];
// Compute the normal for every face.
// While doing that, create a new vertex for every face vertex
for (let i = 0; i < numFaces; ++i) {
const n1 = getNextIndex() * 3;
const n2 = getNextIndex() * 3;
const n3 = getNextIndex() * 3;
const v1 = positions.slice(n1, n1 + 3);
const v2 = positions.slice(n2, n2 + 3);
const v3 = positions.slice(n3, n3 + 3);
faceNormals.push(m4.normalize(m4.cross(m4.subtractVectors(v1, v2), m4.subtractVectors(v3, v2))));
}
let tempVerts = {};
let tempVertNdx = 0;
// this assumes vertex positions are an exact match
function getVertIndex(x, y, z) {
const vertId = x + "," + y + "," + z;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
return newNdx;
}
// We need to figure out the shared vertices.
// It's not as simple as looking at the faces (triangles)
// because for example if we have a standard cylinder
//
//
// 3-4
// / \
// 2 5 Looking down a cylinder starting at S
// | | and going around to E, E and S are not
// 1 6 the same vertex in the data we have
// \ / as they don't share UV coords.
// S/E
//
// the vertices at the start and end do not share vertices
// since they have different UVs but if you don't consider
// them to share vertices they will get the wrong normals
const vertIndices = [];
for (let i = 0; i < numVerts; ++i) {
const offset = i * 3;
const vert = positions.slice(offset, offset + 3);
vertIndices.push(getVertIndex(vert));
}
// go through every vertex and record which faces it's on
const vertFaces = [];
getNextIndex.reset();
for (let i = 0; i < numFaces; ++i) {
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
let faces = vertFaces[sharedNdx];
if (!faces) {
faces = [];
vertFaces[sharedNdx] = faces;
}
faces.push(i);
}
}
// now go through every face and compute the normals for each
// vertex of the face. Only include faces that aren't more than
// maxAngle different. Add the result to arrays of newPositions,
// newTexcoords and newNormals, discarding any vertices that
// are the same.
tempVerts = {};
tempVertNdx = 0;
const newPositions = [];
const newTexcoords = [];
const newNormals = [];
function getNewVertIndex(x, y, z, nx, ny, nz, u, v) {
const vertId =
x + "," + y + "," + z + "," +
nx + "," + ny + "," + nz + "," +
u + "," + v;
const ndx = tempVerts[vertId];
if (ndx !== undefined) {
return ndx;
}
const newNdx = tempVertNdx++;
tempVerts[vertId] = newNdx;
newPositions.push(x, y, z);
newNormals.push(nx, ny, nz);
newTexcoords.push(u, v);
return newNdx;
}
const newVertIndices = [];
getNextIndex.reset();
const maxAngleCos = Math.cos(maxAngle);
// for each face
for (let i = 0; i < numFaces; ++i) {
// get the normal for this face
const thisFaceNormal = faceNormals[i];
// for each vertex on the face
for (let j = 0; j < 3; ++j) {
const ndx = getNextIndex();
const sharedNdx = vertIndices[ndx];
const faces = vertFaces[sharedNdx];
const norm = [0, 0, 0];
faces.forEach(faceNdx => {
// is this face facing the same way
const otherFaceNormal = faceNormals[faceNdx];
const dot = m4.dot(thisFaceNormal, otherFaceNormal);
if (dot > maxAngleCos) {
m4.addVectors(norm, otherFaceNormal, norm);
}
});
m4.normalize(norm, norm);
const poffset = ndx * 3;
const toffset = ndx * 2;
newVertIndices.push(getNewVertIndex(
positions[poffset + 0], positions[poffset + 1], positions[poffset + 2],
norm[0], norm[1], norm[2],
texcoords[toffset + 0], texcoords[toffset + 1]));
}
}
return {
position: newPositions,
texcoord: newTexcoords,
normal: newNormals,
indices: newVertIndices,
};
}
function makeIndexedIndicesFn(arrays) {
const indices = arrays.indices;
let ndx = 0;
const fn = function() {
return indices[ndx++];
};
fn.reset = function() {
ndx = 0;
};
fn.numElements = indices.length;
return fn;
}
function makeUnindexedIndicesFn(arrays) {
let ndx = 0;
const fn = function() {
return ndx++;
};
fn.reset = function() {
ndx = 0;
}
fn.numElements = arrays.positions.length / 3;
return fn;
}
function makeIndiceIterator(arrays) {
return arrays.indices
? makeIndexedIndicesFn(arrays)
: makeUnindexedIndicesFn(arrays);
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/3d-math.js"></script>
<canvas></canvas>

WebGL: smoothly fade lines out of canvas

I'm a beginner at WebGL programming.
I've made a web application in Three.JS that draws a sin wave onto a canvas with occasional noise. After they've been drawn, I fade them away. The final effect looks something like this:
I'm trying to make the application in WebGL because of speed issues with Three.JS. I am able to draw one plain sin wave in WebGL but don't know how to achieve the same effect where I can draw a single wave, keep it in the buffer somehow, and fade it away.
This is what I currently have (in WebGL):
Also, here is the relevant code:
this.gl;
try {
this.gl = this.canvas.getContext('experimental-webgl',{antialias: false});
} catch (e) {
alert('WebGL not supported.');
}
//set position of vertices in clip coordinates
this.vtxShaderSrc = "\n\
attribute vec2 position;\n\
uniform vec2 viewport;\n\
\n\
void main(void){\n\
\n\
gl_Position = vec4((position/viewport)*2.0-1.0, 0.0, 1.0);\n\
}";
//fragment shader returns the color of pixel
this.fmtShaderSrc = "\n\
precision mediump float;\n\
\n\
\n\
\n\
void main(void){\n\
int r = 255;\n\
int g = 255;\n\
int b = 255;\n\
gl_FragColor = vec4(r/255,g/255,b/255,1.);\n\
}";
this.getShader = function(source, type){
var shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
return shader;
}
this.vtxShader = this.getShader(this.vtxShaderSrc, this.gl.VERTEX_SHADER);
this.fmtShader = this.getShader(this.fmtShaderSrc, this.gl.FRAGMENT_SHADER);
this.program = this.gl.createProgram();
//attach fragment and vertex shader to program
this.gl.attachShader(this.program, this.vtxShader);
this.gl.attachShader(this.program, this.fmtShader);
//link program to WebGL
this.gl.linkProgram(this.program);
//get position attribute and enable it in vertex shader
this._position = this.gl.getAttribLocation(this.program, 'position');
this.gl.enableVertexAttribArray(this._position);
//tell WebGL to use this program
this.gl.useProgram(this.program);
//create buffers
this.vertexBuffer = this.gl.createBuffer();
this.facesBuffer = this.gl.createBuffer();
this.lineVertices = [];
this.faceCount = [];
//bind them to WebGL
this.bindVertexBuffer = function(){
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(this.lineVertices), this.gl.STREAM_DRAW);
}
this.bindFacesBuffer = function(){
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.faceCount), this.gl.STREAM_DRAW);
}
this.bindVertexBuffer();
this.bindFacesBuffer();
//set background color to black
this.gl.clearColor(0.0,0.0,0.0,1.0);
//draw on canvas
this.draw = function(){
this.gl.enable(this.gl.BLEND);
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.vertexAttribPointer(this._position, 2, this.gl.FLOAT, false, 8*2, 0);
var loc = this.gl.getUniformLocation(this.program, 'viewport');
this.gl.uniform2f(loc, this.canvas.width, this.canvas.height);
//draw only if number of lines is greater than 0
if(this.faceCount.length > 0){
this.gl.drawElements(this.gl.LINE_STRIP, this.faceCount.length/4, this.gl.UNSIGNED_SHORT, 0);
}
this.gl.disable(this.gl.BLEND);
}
//update vertices and faces so next call to this.draw() updates the wave
this.update = function(newPts){
this.lineVertices = newPts;
this.bindVertexBuffer();
var faces = [];
for(var i = 0; i < this.lineVertices.length; i++) faces.push(i);
this.faceCount = faces;
this.bindFacesBuffer();
}
Any help/pointers are appreciated. Thanks
It's hard to give an answer as there's an infinite number of ways to do it but basically WebGL is just a rasteration API so if you want something to fade out overtime you have to render it every frame and over time draw things you want to fade out with a more transparency.
In pseudo code
for each thing to draw
compute its age
draw more transparent the older it is
(optionally, delete it if it's too old)
Here's a canvas 2d version to keep it simple
var ctx = document.querySelector("canvas").getContext("2d")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = ctx.canvas.width * thing.x;
var y = ctx.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = makeCSSRGBAColor(0, 0, 0, 1. - lerp);
drawCircle(ctx, x, y, radius, color);
});
}
function drawCircle(ctx, x, y, radius, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
function makeCSSRGBAColor(r, g, b, a) {
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<canvas></canvas>
WebGL is no different, just replace ctx.clearRect with gl.clear and drawCircle with some function that draws a circle.
Here's the WebGL version of the same program
var gl = document.querySelector("canvas").getContext("webgl")
var currentTime = 0; // in seconds
var ageLimit = 1; // 1 second
var birthDuration = 0.2; // 0.2 seconds
var birthTimer = 0;
var thingsToDraw = [];
var vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
var fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
var program = twgl.createProgramFromSources(gl, [vs, fs]);
var positionLocation = gl.getAttribLocation(program, "position");
var colorLocation = gl.getUniformLocation(program, "u_color");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
// make a circle of triangles
var numAround = 60;
var verts = [];
for (var i = 0; i < numAround; ++i) {
addPoint(verts, i / numAround, 1);
addPoint(verts, (i + 1) / numAround, 1);
addPoint(verts, i / numAround, 0);
}
var numVerts = verts.length / 2;
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
function addPoint(verts, angleZeroToOne, radius) {
var angle = angleZeroToOne * Math.PI * 2;
verts.push(Math.cos(angle) * radius, Math.sin(angle) * radius);
}
function addThingToDraw() {
thingsToDraw.push({
timeOfBirth: currentTime,
x: Math.random(),
y: Math.random(),
});
}
function computeAge(thing) {
return currentTime - thing.timeOfBirth;
}
function removeOldThings() {
while(thingsToDraw.length > 0) {
var age = computeAge(thingsToDraw[0]);
if (age < ageLimit) {
break;
}
// remove thing that's too old
thingsToDraw.shift();
}
}
function drawThings() {
gl.clear(gl.CLEAR_BUFFER_BIT);
thingsToDraw.forEach(function(thing) {
var age = computeAge(thing);
var lerp = age / ageLimit;
var x = gl.canvas.width * thing.x;
var y = gl.canvas.height * thing.y;
var radius = 10 + lerp * 10; // 10 to 20
var color = [0, 0, 0, 1 - lerp];
drawCircle(gl, x, y, radius, color);
});
}
function drawCircle(gl, x, y, radius, color) {
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var matrix = [
2 / gl.canvas.width * radius, 0, 0, 0,
0, 2 / gl.canvas.height * radius, 0, 0,
0, 0, 1, 0,
x / gl.canvas.width * 2 - 1, y / gl.canvas.height * 2 - 1, 0, 1,
];
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, matrix);
gl.uniform4fv(colorLocation, color);
gl.drawArrays(gl.TRIANGLES, 0, numVerts);
}
function ortho(width, height) {
return [
2 / (width), 0, 0, 0,
0, 2 / (height), 0, 0,
0, 0, 1, 0, 0,
(width) / (-width), (height) / (-height), -1, 1,
];
}
var then = 0;
function process(time) {
currentTime = time * 0.001;
var deltaTime = currentTime - then;
then = currentTime;
birthTimer -= deltaTime;
if (birthTimer <= 0) {
addThingToDraw();
birthTimer = birthDuration;
}
removeOldThings();
drawThings();
requestAnimationFrame(process);
}
requestAnimationFrame(process);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>
I didn't want to include a matrix library but you can read about matrices here and because almost everyone into issues when graduating from one shape/shader to 2 you probably want to read this about drawing multiple things

Is it possible to make this 'Water Shader Animation' not spherical?

http://codepen.io/Khangeldy/pen/gPJoxJ
JS
// init camera, scene, renderer
var scene, camera, renderer;
scene = new THREE.Scene();
var fov = 75,
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
camera.position.z = 100;
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xc4c4c4);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var clock = new THREE.Clock();
var tuniform = {
iGlobalTime: {
type: 'f',
value: 0.1
},
iResolution: {
type: 'v2',
value: new THREE.Vector2()
},
iMouse: {
type: 'v4',
value: new THREE.Vector2()
}
};
// Mouse position in - 1 to 1
renderer.domElement.addEventListener('mousedown', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.iMouse.value.x = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.iMouse.value.y = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
renderer.domElement.addEventListener('mouseup', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.iMouse.value.z = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.iMouse.value.w = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
// resize canvas function
window.addEventListener('resize',function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
tuniform.iResolution.value.x = window.innerWidth;
tuniform.iResolution.value.y = window.innerHeight;
// Create Plane
var material = new THREE.ShaderMaterial({
uniforms: tuniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(mesh);
// draw animation
function render(time) {
tuniform.iGlobalTime.value += clock.getDelta();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
I'm wanting to know if its possible to edit this animation, so the horizon is flat (so it doesnt look like a ball of water, instead like the horizon of an ocean?) and ontop of this, is it possible to make the camera 'still'? Thanks
Yes, it's possible.
All you have to do is experiment with the variables.
// init camera, scene, renderer
var scene, camera, renderer;
scene = new THREE.Scene();
var fov = 75,
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
camera.position.z = 100;
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xc4c4c4);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var clock = new THREE.Clock();
var tuniform = {
time: {
type: 'f',
value: 0.1
},
resolution: {
type: 'v2',
value: new THREE.Vector2()
},
mouse: {
type: 'v4',
value: new THREE.Vector2()
}
};
// Mouse position in - 1 to 1
renderer.domElement.addEventListener('mousedown', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.mouse.value.x = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.mouse.value.y = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
renderer.domElement.addEventListener('mouseup', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.mouse.value.z = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.mouse.value.w = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
// resize canvas function
window.addEventListener('resize',function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
tuniform.resolution.value.x = window.innerWidth;
tuniform.resolution.value.y = window.innerHeight;
// Create Plane
var material = new THREE.ShaderMaterial({
uniforms: tuniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(mesh);
// draw animation
function render(time) {
tuniform.time.value += clock.getDelta();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
body {
overflow: hidden;
margin: 0;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script>
<!-- THIS is OPENGL Shading language scripts -->
<script id="vertex-shader" type="no-js">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fragment-shader" type="no-js">
#ifdef GL_ES
precision mediump float;
#endif
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;
const int NUM_STEPS = 8;
const float PI = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM = 0.1 / resolution.x;
// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 2.0;
const float SEA_SPEED = 0.8;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
const float SKY_INTENSITY = 1.0;
#define SEA_TIME time * SEA_SPEED
// math
mat4 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat4 m;
m[0] = vec4(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x,0.0);
m[1] = vec4(-a2.y*a1.x,a1.y*a2.y,a2.x,0.0);
m[2] = vec4(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y,0.0);
m[3] = vec4(0.0,0.0,0.0,1.0);
return m;
}
vec3 rotate(vec3 v, mat4 m) {
return vec3(dot(v,m[0].xyz),dot(v,m[1].xyz),dot(v,m[2].xyz));
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*(3.0-2.0*f);
return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}
// lighting
float diffuse(vec3 n,vec3 l,float p) { return pow(dot(n,l) * 0.4 + 0.6,p); }
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (3.1415 * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
// sky
vec3 sky_color(vec3 e) {
e.y = max(e.y,0.0);
vec3 ret;
ret.x = pow(1.0-e.y,2.0);
ret.y = 1.0-e.y;
ret.z = 0.6+(1.0-e.y)*0.4;
return ret * SKY_INTENSITY;
}
// sea
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0-abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv,swv,wv);
return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
mat2 m = mat2(1.6,1.2,-1.2,1.6);
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 sea_color(in vec3 p, in vec3 n, in vec3 eye, in vec3 dist) {
float fresnel_o = 1.0 - max(dot(n,-eye),0.0);
float fresnel = pow(fresnel_o,3.0) * 0.65;
// reflection
vec3 refl = sky_color(reflect(eye,n));
// color
vec3 ret = SEA_BASE;
ret = mix(ret,refl,fresnel);
// wave peaks
float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
ret += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
return ret;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float hftracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) return tx;
float hm = map(ori + dir * tm);
float tmid = 0.0;
for(int i = 0; i < NUM_STEPS; i++) {
tmid = mix(tm,tx, hm/(hm-hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
}
return tmid;
}
// main
void main(void)
{
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv = 1.0 - uv * 2.0;
uv.x *= resolution.x / resolution.y;
//uv = (surfacePosition+vec2(0., .5))*17. + 5E-3*(pow(length(surfacePosition+vec2(0. ,0.5)), -2.));
uv.y *= -1.;
//uv.y += -2.;
// ray
vec3 ang = vec3(0.0,0.003, pow(time, 0.6));
ang = vec3(0.0,clamp(2.0-mouse.y*0.01,-0.3,PI),mouse.x*0.01);
vec3 ori = vec3(0.0,3.5,time*.05);
vec3 dir = normalize(vec3(uv.xy,-2.0));
dir.z -= length(uv) * 0.15;
//dir = rotate(normalize(dir),ang);
// tracing
vec3 p;
float dens = hftracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(p, dot(dist,dist)*EPSILON_NRM);
// color
vec3 color = sea_color(p,n,dir,dist);
vec3 light = normalize(vec3(0.0,1.0,0.8));
color += vec3(diffuse(n,light,80.0) * SEA_WATER_COLOR) * 0.12;
color += vec3(specular(n,light,dir,60.0));
// post
color = mix(sky_color(dir),color,pow(smoothstep(0.0,-0.05,dir.y),0.3));
color = pow(color,vec3(0.75));
gl_FragColor = vec4(color,1.0);
}
</script>
External Demo
https://jsfiddle.net/nanilab/uz6yo2w3/

Categories