OpenCV to WebGL - opencv

I'm trying to use OpenCV with WebGL.
In OpenCV, I'm looking for a projection (a priori the projection matrix of the camera) that I get using SolvePnp.
When I display the projection obtained in OpenCV with the 'projectPoints' function, everything is perfectly stalled.
The SolvePnp function returns 3 values ​​for the rotation and 3 values ​​for the translation. I also get the cameraMatrix(matrix 3x3) using the values ​​fx, fy, cx, cy (see:https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html#solvepnp).
Once all these values ​​are recovered, I try to use them with WebGL. But for the moment it does not work.
In WebGL, I calculate the projectionMatrix in two different ways:
m4.perspective (28 / 180.0 * Math.PI, aspect, 0.1, 1000);
or through the following example (https://blog.noctua-software.com/opencv-opengl-projection-matrix.html)
openCVToProjectionMatrix (fx, fy, cx, cy, 0.1, 1000, wSource, hSource);
Then I create the cameraMatrix and I apply to it the values ​​recovered since OpenCV.
I compute the ViewMatrix by doing an inverseMatrix.
Then I compute the viewProjectionMatrix by multiplying projectionMatrix by viewMatrix.
I calculate ModelMatrix by simply viewProjectionMatrix.
And I apply everything on my plan (2 triangles) with the following values:
var size = 1.0
x: -size * 0.5
y: -size * 0.5
width: size
height: size
then I add:
gl.uniformMatrix4fv (matrixLocation, false, matrixModel)
Unfortunately it does not work.
My questions :
Do I apply the matrices properly or are there any errors at this level?
Are there other parameters to consider?
Do I have to apply a translation on the camera?
Is there an order to be complied with for matrix calculations (start with scale, then rotation and finally translation)?
And finally, can someone help me?
A sample of code
// Source Dimensions(Webcam)
var wSrc = 360, hSrc = 640;
// Trigger Dimensions
var wTrigger = 602, hTrigger = 452;
// OpenCV values for projectionMatrix
var fx = 603.92035729, fy = 605.26722937;
var cx = 179.804103189, cy = 320.14721692;
// Values from SolvePnp(openCV)
var rVecs = [0.464472599644,-0.210231064819,-0.0689626255534];
var tVecs = [-0.61758832993,-0.567979295625,2.78430675542];
// -------------------------------------------------------------------------------------
var vertexShader = `
attribute vec4 a_position;
attribute vec4 a_color;
uniform mat4 u_matrix;
void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
}
`;
// -------------------------------------------------------------------------------------
var fragmentShader = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`;
// *************************************************************************************
// *************************************************************************************
// *************************************************************************************
var m4 = {
perspective: function(fieldOfViewInRadians, aspect, near, far) {
var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
var rangeInv = 1.0 / (near - far);
return [
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
];
},
projection: function(width, height, depth) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1,
];
},
multiply: function(a, b) {
var a00 = a[0 * 4 + 0];
var a01 = a[0 * 4 + 1];
var a02 = a[0 * 4 + 2];
var a03 = a[0 * 4 + 3];
var a10 = a[1 * 4 + 0];
var a11 = a[1 * 4 + 1];
var a12 = a[1 * 4 + 2];
var a13 = a[1 * 4 + 3];
var a20 = a[2 * 4 + 0];
var a21 = a[2 * 4 + 1];
var a22 = a[2 * 4 + 2];
var a23 = a[2 * 4 + 3];
var a30 = a[3 * 4 + 0];
var a31 = a[3 * 4 + 1];
var a32 = a[3 * 4 + 2];
var a33 = a[3 * 4 + 3];
var b00 = b[0 * 4 + 0];
var b01 = b[0 * 4 + 1];
var b02 = b[0 * 4 + 2];
var b03 = b[0 * 4 + 3];
var b10 = b[1 * 4 + 0];
var b11 = b[1 * 4 + 1];
var b12 = b[1 * 4 + 2];
var b13 = b[1 * 4 + 3];
var b20 = b[2 * 4 + 0];
var b21 = b[2 * 4 + 1];
var b22 = b[2 * 4 + 2];
var b23 = b[2 * 4 + 3];
var b30 = b[3 * 4 + 0];
var b31 = b[3 * 4 + 1];
var b32 = b[3 * 4 + 2];
var b33 = b[3 * 4 + 3];
return [
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
];
},
translation: function(tx, ty, tz) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1,
];
},
xRotation: function(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1,
];
},
yRotation: function(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1,
];
},
zRotation: function(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
];
},
scaling: function(sx, sy, sz) {
return [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1,
];
},
translate: function(m, tx, ty, tz) {
return m4.multiply(m, m4.translation(tx, ty, tz));
},
xRotate: function(m, angleInRadians) {
return m4.multiply(m, m4.xRotation(angleInRadians));
},
yRotate: function(m, angleInRadians) {
return m4.multiply(m, m4.yRotation(angleInRadians));
},
zRotate: function(m, angleInRadians) {
return m4.multiply(m, m4.zRotation(angleInRadians));
},
scale: function(m, sx, sy, sz) {
return m4.multiply(m, m4.scaling(sx, sy, sz));
},
inverse: function(m) {
var m00 = m[0 * 4 + 0];
var m01 = m[0 * 4 + 1];
var m02 = m[0 * 4 + 2];
var m03 = m[0 * 4 + 3];
var m10 = m[1 * 4 + 0];
var m11 = m[1 * 4 + 1];
var m12 = m[1 * 4 + 2];
var m13 = m[1 * 4 + 3];
var m20 = m[2 * 4 + 0];
var m21 = m[2 * 4 + 1];
var m22 = m[2 * 4 + 2];
var m23 = m[2 * 4 + 3];
var m30 = m[3 * 4 + 0];
var m31 = m[3 * 4 + 1];
var m32 = m[3 * 4 + 2];
var m33 = m[3 * 4 + 3];
var tmp_0 = m22 * m33;
var tmp_1 = m32 * m23;
var tmp_2 = m12 * m33;
var tmp_3 = m32 * m13;
var tmp_4 = m12 * m23;
var tmp_5 = m22 * m13;
var tmp_6 = m02 * m33;
var tmp_7 = m32 * m03;
var tmp_8 = m02 * m23;
var tmp_9 = m22 * m03;
var tmp_10 = m02 * m13;
var tmp_11 = m12 * m03;
var tmp_12 = m20 * m31;
var tmp_13 = m30 * m21;
var tmp_14 = m10 * m31;
var tmp_15 = m30 * m11;
var tmp_16 = m10 * m21;
var tmp_17 = m20 * m11;
var tmp_18 = m00 * m31;
var tmp_19 = m30 * m01;
var tmp_20 = m00 * m21;
var tmp_21 = m20 * m01;
var tmp_22 = m00 * m11;
var tmp_23 = m10 * m01;
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
return [
d * t0,
d * t1,
d * t2,
d * t3,
d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)),
d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
(tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
(tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
(tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)),
d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
(tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
(tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
(tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
(tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
];
},
vectorMultiply: function(v, m) {
var dst = [];
for (var i = 0; i < 4; ++i) {
dst[i] = 0.0;
for (var j = 0; j < 4; ++j)
dst[i] += v[j] * m[j * 4 + i];
}
return dst;
},
};
// *************************************************************************************
// *************************************************************************************
// *************************************************************************************
initGL();
// -------------------------------------------------------------------------------------
function initializeWebGL(canvasName) {
var canvas = document.getElementById(canvasName);
var gl = null;
try {
gl = canvas.getContext("webgl");
}
catch (error) {
console.log("Error getContext WebGL", error);
}
if (!gl) {
throw new Error("Could not get WebGL context!");
}
return gl;
}
function createShader(gl, shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var infoLog = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error("An error occurred compiling the shader: " + infoLog);
}
else {
return shader;
}
}
function createGlslProgram(gl, vertexSource, fragmentSource) {
var program = gl.createProgram();
gl.attachShader(program, createShader(gl, vertexSource, gl.VERTEX_SHADER));
gl.attachShader(program, createShader(gl, fragmentSource, gl.FRAGMENT_SHADER));
gl.linkProgram(program);
gl.validateProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var infoLog = gl.getProgramInfoLog(program);
gl.deleteProgram(program);
throw new Error("An error occurred linking the program: " + infoLog);
}
else {
return program;
}
}
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
function initGL() {
var gl = initializeWebGL("canvasGL");
var program = createGlslProgram(gl, vertexShader, fragmentShader);
// ------------------------------------------------------------------------------
resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio);
// ------------------------------------------------------------------------------
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");
var positionBuffer = gl.createBuffer();
// ------------------------------------------------------------------------------
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// ------------------------------------------------------------------------------
gl.useProgram(program);
// ------------------------------------------------------------------------------
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// ------------------------------------------------------------------------------
initFlatPlane(gl, positionAttributeLocation, matrixLocation);
}
function initFlatPlane(gl, positionAttributeLocation, matrixLocation){
var size = 3;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset)
// ------------------------------------------------------------------------------
// projectionMatrix
var matOpenCV = openCVCameraMatrixToProjectionMatrix(fx, fy, cx, cy, 1000, 0.1, wSrc, hSrc);
var projectionMatrix = matOpenCV[0].concat(matOpenCV[1]).concat(matOpenCV[2]).concat(matOpenCV[3]);
console.log("projectionMatrix :", projectionMatrix);
// ------------------------------------------------------------------------------
// Compute a matrix for the camera
var cameraMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
cameraMatrix = m4.scale(cameraMatrix, 1, wTrigger/hTrigger, 1);
cameraMatrix = m4.xRotate(cameraMatrix, rVecs[0]);
cameraMatrix = m4.yRotate(cameraMatrix, rVecs[1]);
cameraMatrix = m4.zRotate(cameraMatrix, rVecs[2]);
cameraMatrix = m4.translate(cameraMatrix, tVecs[0], tVecs[1], tVecs[2]);
console.log("cameraMatrix", cameraMatrix);
// ------------------------------------------------------------------------------
// Make a view matrix from the camera matrix
var viewMatrix = m4.inverse(cameraMatrix);
console.log("viewMatrix", viewMatrix);
// ------------------------------------------------------------------------------
// Compute a view projection matrix
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
console.log("viewProjectionMatrix", viewProjectionMatrix);
// ------------------------------------------------------------------------------
// modelMatrix
var modelMatrix = m4.translate(viewProjectionMatrix, 0.0, 0.0, 0.0);
console.log("modelMatrix", modelMatrix);
// ------------------------------------------------------------------------------
var size = 1.0
setRectangle(gl, -size * .5, -size * .5, size, size);
// Let modelMatrix
gl.uniformMatrix4fv(matrixLocation, false, modelMatrix);
// Draw
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
var z = 0;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1, z,
x2, y1, z,
x1, y2, z,
x1, y2, z,
x2, y1, z,
x2, y2, z,
]), gl.STATIC_DRAW);
}
function resizeCanvasToDisplaySize(canvas, multiplier) {
multiplier = multiplier || 1;
var width = canvas.clientWidth * multiplier | 0;
var height = canvas.clientHeight * multiplier | 0;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
return true;
}
return false;
}
function openCVCameraMatrixToProjectionMatrix(fx, fy, cx, cy, zfar, znear, width, height){
var m = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
];
m[0][0] = 2.0 * fx / width;
m[0][1] = 0.0;
m[0][2] = 0.0;
m[0][3] = 0.0;
m[1][0] = 0.0;
m[1][1] = -2.0 * fy / height;
m[1][2] = 0.0;
m[1][3] = 0.0;
m[2][0] = 1.0 - 2.0 * cx / width;
m[2][1] = 2.0 * cy / height - 1.0;
m[2][2] = (zfar + znear) / (znear - zfar);
m[2][3] = -1.0;
m[3][0] = 0.0;
m[3][1] = 0.0;
m[3][2] = 2.0 * zfar * znear / (znear - zfar);
m[3][3] = 0.0;
return m;
}
body {
margin:0;
padding:0;
background-color:lightgray;
}
#canvasGL, img {
position:absolute;
width:360px;
height:640px;
}
img {
opacity: 0.2;
}
#canvasBackground {
border: solid 1px darkGray;
}
<body>
<img src="http://www.indelebil.fr/tmp/preview_python_02.jpg" />
<canvas id="canvasGL"></canvas>
</body>
The result
Thanks for your help !!!!

Related

Artefacts when rendering to a framebuffer with alpha blending using WebGL2

I am trying to draw 2D metaballs using WebGL2. I render a bunch of quads with transparent radial gradient and gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) to a separate framebuffer. I then use the resulting texture in a fullscreen quad, where I decide if pixel should be rendered based on it's alpha value like so:
vec4 inputColor = texture(u_texture, v_uv);
float cutoffThreshold = 0.14;
float cutoff = step(cutoffThreshold, inputColor.a);
float threshold = 0.005;
outputColor = mix(
vec4(1, 0, 0, 1),
vec4(0, 0, 1, 1),
cutoff
);
While this kinda works, it produces really noticeable artefacts along the edges:
I think the problem lays in my blending operation. I tried enabling blending only when drawing to my framebuffer and disableing it when rendering my main quad without much success.
Here is my program:
const CONFIG = {
ballsCount: 10,
ballRadius: isMobileBrowser() ? 75 : 200,
gravity: 0.1,
lineWidth: innerWidth / 2,
startVelocityX: { min: 0, max: 0.1 },
startVelocityY: { min: 1, max: 3 },
}
const contentWrapper = document.querySelector('.content')
const canvas = document.createElement('canvas')
const gl = canvas.getContext('webgl2')
const dpr = devicePixelRatio > 2.5 ? 2.5 : devicePixelRatio
if (!gl) {
showWebGL2NotSupported()
}
const lineVertexArrayObject = gl.createVertexArray()
const quadVertexArrayObject = gl.createVertexArray()
const ballsVertexArrayObject = gl.createVertexArray()
const ballsOffsetsBuffer = gl.createBuffer()
let oldTime = 0
let lineAngle = 0
// WebGL Programs
let lineWebGLProgram
let quadWebGLProgram
let ballsWebGLProgram
let quadTextureUniformLoc
let lineAngleUniformLoc
let lineVertexArray
let ballsOffsetsArray
// Not for rendering, just storing the balls velocities
let ballsVelocitiesArray
/* ------- Create horizontal line WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
uniform vec2 u_resolution;
uniform float u_angle;
in vec4 a_position;
mat4 rotationZ( in float angle ) {
return mat4(
cos(angle), -sin(angle), 0.0, 0.0,
sin(angle), cos(angle), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
void main () {
gl_Position = u_projectionMatrix * (rotationZ(u_angle) * a_position + vec4(u_resolution.xy / 2.0, 0.0, 1.0));
}
`,
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
out vec4 outputColor;
void main () {
outputColor = vec4(0, 0, 1, 1);
}
`,
})
lineWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign horizontal line WebGL attributes ------- */
{
lineVertexArray = new Float32Array([-CONFIG.lineWidth / 2, 0, CONFIG.lineWidth / 2, 0])
const vertexBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(lineWebGLProgram, 'a_position')
gl.bindVertexArray(lineVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, lineVertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindVertexArray(null)
}
/* ------- Create metaballs WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
in vec4 a_position;
in vec4 a_offsetPosition;
in vec2 a_uv;
out vec2 v_uv;
void main () {
vec4 correctOffsetedPosition = a_offsetPosition + a_position;
gl_Position = u_projectionMatrix * correctOffsetedPosition;
v_uv = a_uv;
}
`
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
in vec2 v_uv;
out vec4 outputColor;
void main () {
float dist = distance(v_uv, vec2(0.5));
float c = 0.5 - dist;
outputColor = vec4(vec3(1.0), c);
}
`
})
ballsWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign metaballs WebGL attributes ------- */
{
const vertexArray = new Float32Array([
-CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2,
-CONFIG.ballRadius / 2, CONFIG.ballRadius / 2,
CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2,
-CONFIG.ballRadius / 2, -CONFIG.ballRadius / 2
])
const uvsArray = makeQuadUVs()
ballsOffsetsArray = new Float32Array(CONFIG.ballsCount * 2)
ballsVelocitiesArray = new Float32Array(CONFIG.ballsCount * 2)
for (let i = 0; i < CONFIG.ballsCount; i++) {
ballsOffsetsArray[i * 2 + 0] = Math.random() * innerWidth
ballsOffsetsArray[i * 2 + 1] = Math.random() * innerHeight
ballsVelocitiesArray[i * 2 + 0] = (Math.random() * 2 - 1) * CONFIG.startVelocityX.max + CONFIG.startVelocityX.min
ballsVelocitiesArray[i * 2 + 1] = Math.random() * CONFIG.startVelocityY.max + CONFIG.startVelocityY.min
}
const vertexBuffer = gl.createBuffer()
const uvsBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(ballsWebGLProgram, 'a_position')
const a_uv = gl.getAttribLocation(ballsWebGLProgram, 'a_uv')
const a_offsetPosition = gl.getAttribLocation(ballsWebGLProgram, 'a_offsetPosition')
gl.bindVertexArray(ballsVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, uvsArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_uv)
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, ballsOffsetsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, ballsOffsetsArray, gl.DYNAMIC_DRAW)
gl.enableVertexAttribArray(a_offsetPosition)
gl.vertexAttribPointer(a_offsetPosition, 2, gl.FLOAT, false, 0, 0)
gl.vertexAttribDivisor(a_offsetPosition, 1)
gl.bindVertexArray(null)
}
/* ------- Create fullscreen quad WebGL program ------- */
{
const vertexShader = makeWebglShader(gl, {
shaderType: gl.VERTEX_SHADER,
shaderSource: `#version 300 es
uniform mat4 u_projectionMatrix;
in vec4 a_position;
in vec2 a_uv;
out vec2 v_uv;
void main () {
gl_Position = u_projectionMatrix * a_position;
v_uv = a_uv;
}
`
})
const fragmentShader = makeWebglShader(gl, {
shaderType: gl.FRAGMENT_SHADER,
shaderSource: `#version 300 es
precision highp float;
uniform sampler2D u_texture;
in vec2 v_uv;
out vec4 outputColor;
void main () {
vec4 inputColor = texture(u_texture, v_uv);
float cutoffThreshold = 0.14;
float cutoff = step(cutoffThreshold, inputColor.a);
float threshold = 0.005;
outputColor = mix(
vec4(1, 0, 0, 1),
vec4(0, 0, 1, 1),
cutoff
);
cutoffThreshold += 0.001;
cutoff = smoothstep(cutoffThreshold - threshold, cutoffThreshold + threshold, inputColor.a);
outputColor = mix(
outputColor,
vec4(1, 0, 0, 1),
cutoff
);
cutoffThreshold += 0.05;
cutoff = smoothstep(cutoffThreshold - threshold, cutoffThreshold + threshold, inputColor.a);
outputColor = mix(
outputColor,
vec4(0, 1, 0, 1),
cutoff
);
// outputColor = mix(inputColor, mix(baseColor, metaballsColor, cutoff), 0.3);
// outputColor = inputColor;
}
`
})
quadWebGLProgram = makeWebglProram(gl, {
vertexShader,
fragmentShader,
})
}
/* ------- Create and assign fullscreen quad WebGL attributes ------- */
{
const vertexArray = new Float32Array([
0, innerHeight / 2,
innerWidth / 2, innerHeight / 2,
innerWidth / 2, 0,
0, innerHeight / 2,
innerWidth / 2, 0,
0, 0
])
const uvsArray = makeQuadUVs()
const vertexBuffer = gl.createBuffer()
const uvsBuffer = gl.createBuffer()
const a_position = gl.getAttribLocation(quadWebGLProgram, 'a_position')
const a_uv = gl.getAttribLocation(quadWebGLProgram, 'a_uv')
gl.bindVertexArray(quadVertexArrayObject)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, vertexArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_position)
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, uvsArray, gl.STATIC_DRAW)
gl.enableVertexAttribArray(a_uv)
gl.vertexAttribPointer(a_uv, 2, gl.FLOAT, false, 0, 0)
gl.bindVertexArray(null)
}
/* ------- Create WebGL texture to render to ------- */
gl.getExtension('EXT_color_buffer_float')
gl.getExtension('EXT_float_blend')
gl.getExtension('OES_texture_float_linear')
const targetTextureWidth = innerWidth * dpr
const targetTextureHeight = innerHeight * dpr
const targetTexture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.FLOAT, null)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.bindTexture(gl.TEXTURE_2D, null)
/* ------- Create WebGL framebuffer to render to ------- */
const framebuffer = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, 0)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
init()
function init () {
document.body.appendChild(canvas)
resize()
window.addEventListener('resize', resize)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// gl.blendEquation(gl.FUNC_SUBTRACT)
const projectionMatrix = makeProjectionMatrix(innerWidth / 2, innerHeight / 2)
let u_projectionMatrix
gl.useProgram(ballsWebGLProgram)
u_projectionMatrix = gl.getUniformLocation(ballsWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
gl.useProgram(null)
gl.useProgram(quadWebGLProgram)
quadTextureUniformLoc = gl.getUniformLocation(quadWebGLProgram, 'u_texture')
gl.uniform1i(quadTextureUniformLoc, 0)
u_projectionMatrix = gl.getUniformLocation(quadWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
gl.useProgram(null)
gl.useProgram(lineWebGLProgram)
u_projectionMatrix = gl.getUniformLocation(lineWebGLProgram, 'u_projectionMatrix')
gl.uniformMatrix4fv(u_projectionMatrix, false, projectionMatrix)
const u_resolution = gl.getUniformLocation(lineWebGLProgram, 'u_resolution')
gl.uniform2f(u_resolution, innerWidth, innerHeight)
lineAngleUniformLoc = gl.getUniformLocation(lineWebGLProgram, 'u_angle')
gl.uniform1f(lineAngleUniformLoc, lineAngle * Math.PI / 180)
gl.useProgram(null)
requestAnimationFrame(renderFrame)
}
let a = true
document.addEventListener('click', () => {
a = !a
})
function renderFrame (ts) {
const dt = ts - oldTime
oldTime = ts
for (let i = 0; i < CONFIG.ballsCount; i++) {
ballsVelocitiesArray[i * 2 + 1] += CONFIG.gravity
ballsOffsetsArray[i * 2 + 0] += ballsVelocitiesArray[i * 2 + 0]
ballsOffsetsArray[i * 2 + 1] += ballsVelocitiesArray[i * 2 + 1]
if (ballsOffsetsArray[i * 2 + 0] < CONFIG.ballRadius / 2) {
ballsOffsetsArray[i * 2 + 0] = CONFIG.ballRadius / 2
ballsVelocitiesArray[i * 2 + 0] *= -1
}
if (ballsOffsetsArray[i * 2 + 0] > innerWidth - CONFIG.ballRadius / 2) {
ballsOffsetsArray[i * 2 + 0] = innerWidth - CONFIG.ballRadius / 2
ballsVelocitiesArray[i * 2 + 0] *= -1
}
if (ballsOffsetsArray[i * 2 + 1] - CONFIG.ballRadius > innerHeight) {
ballsOffsetsArray[i * 2 + 1] = -CONFIG.ballRadius
ballsVelocitiesArray[i * 2 + 1] = 5 + Math.random() * 3
}
}
checkLine()
gl.bindBuffer(gl.ARRAY_BUFFER, ballsOffsetsBuffer)
gl.bufferData(gl.ARRAY_BUFFER, ballsOffsetsArray, gl.DYNAMIC_DRAW)
if (a) {
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)
}
gl.viewport(0, 0, targetTextureWidth, targetTextureHeight)
gl.clearColor(0.1, 0.1, 0.1, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.bindVertexArray(ballsVertexArrayObject)
gl.useProgram(ballsWebGLProgram)
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, CONFIG.ballsCount)
gl.bindVertexArray(null)
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
gl.viewport(0, 0, canvas.width, canvas.height)
if (a) {
gl.bindVertexArray(quadVertexArrayObject)
gl.useProgram(quadWebGLProgram)
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
gl.drawArrays(gl.TRIANGLES, 0, 6)
gl.useProgram(null)
gl.bindVertexArray(null)
gl.bindTexture(gl.TEXTURE_2D, null)
}
lineAngle = Math.sin(ts * 0.001) * 30
gl.bindVertexArray(lineVertexArrayObject)
gl.useProgram(lineWebGLProgram)
gl.uniform1f(lineAngleUniformLoc, -lineAngle * Math.PI / 180)
gl.drawArrays(gl.LINES, 0, 2)
gl.useProgram(null)
gl.bindVertexArray(null)
requestAnimationFrame(renderFrame)
}
function getLineBounds () {
const x1 = lineVertexArray[0]
const y1 = lineVertexArray[1]
const x2 = lineVertexArray[2]
const y2 = lineVertexArray[3]
if (lineAngle === 0) {
const minX = Math.min(x1, x2)
const minY = Math.min(y1, y2)
const maxX = Math.max(x1, x2)
const maxY = Math.max(y1, y2)
return {
x: x1 + minX,
y: y1 + minY,
width: maxX - minX,
height: maxY - minY,
}
} else {
const rotation = lineAngle * Math.PI / 180
const sin = Math.sin(rotation)
const cos = Math.cos(rotation)
const x1r = cos * x1 + sin * y1
const x2r = cos * x2 + sin * y2
const y1r = cos * y1 + sin * x1
const y2r = cos * y2 + sin * x2
const x = innerWidth / 2 + x1 + Math.min(x1r, x2r) + CONFIG.lineWidth / 2
const y = innerHeight / 2 + y1 + Math.min(y1r, y2r) + CONFIG.lineWidth / 2
const width = Math.max(x1r, x2r) - Math.min(x1r, x2r)
const height = Math.max(y1r, y2r) - Math.min(y1r, y2r)
return {
x,
y,
width,
height,
}
}
}
function checkLine () {
const lineBounds = getLineBounds()
const ballRadius = CONFIG.ballRadius / 7
for (let i = 0; i < CONFIG.ballsCount; i++) {
const ballx = ballsOffsetsArray[i * 2 + 0]
const bally = ballsOffsetsArray[i * 2 + 1]
const ballvx = ballsVelocitiesArray[i * 2 + 0]
const ballvy = ballsVelocitiesArray[i * 2 + 1]
if (ballx + ballRadius / 2 > lineBounds.x && ballx - ballRadius / 2 < lineBounds.x + lineBounds.width) {
const lineRotation = lineAngle * Math.PI / 180
const cos = Math.cos(lineRotation)
const sin = Math.sin(lineRotation)
let x = ballx - innerWidth / 2
let y = bally - innerHeight / 2
let vx1 = cos * ballvx + sin * ballvy
let vy1 = cos * ballvy - sin * ballvx
let y1 = cos * y - sin * x
if (y1 > -ballRadius / 2 && y1 < vy1) {
// debugger
const x2 = cos * x + sin * y
y1 = -ballRadius / 2
vy1 *= -0.45
x = cos * x2 - sin * y1
y = cos * y1 + sin * x2
ballsVelocitiesArray[i * 2 + 0] = cos * vx1 - sin * vy1
ballsVelocitiesArray[i * 2 + 1] = cos * vy1 + sin * vx1
ballsOffsetsArray[i * 2 + 0] = innerWidth / 2 + x
ballsOffsetsArray[i * 2 + 1] = innerHeight / 2 + y
}
}
}
}
function resize () {
canvas.width = innerWidth * dpr
canvas.height = innerHeight * dpr
canvas.style.width = `${innerWidth}px`
canvas.style.height = `${innerHeight}px`
}
/* ------- WebGL helpers ------- */
function makeQuadUVs () {
return new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1])
}
function makeWebglShader (gl, { shaderType, shaderSource }) {
const shader = gl.createShader(shaderType)
gl.shaderSource(shader, shaderSource)
gl.compileShader(shader)
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if (success) {
return shader
}
console.error(`
Error in ${shaderType === gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:
${gl.getShaderInfoLog(shader)}
`)
gl.deleteShader(shader)
}
function makeWebglProram (gl, { vertexShader, fragmentShader }) {
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
const success = gl.getProgramParameter(program, gl.LINK_STATUS)
if (success) {
return program
}
console.error(gl.getProgramInfoLog(program))
gl.deleteProgram(program)
}
function makeProjectionMatrix (width, height) {
return new Float32Array([
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 0, 0,
-1, 1, 0, 1,
])
}
function showWebGL2NotSupported () {
const errorMessageWrapper = document.createElement('div')
if (isIOS()) {
const iOSVersion = getIOSVersion().major
if (iOSVersion === 13) {
errorMessageWrapper.innerHTML = `
<p>Please update your device to iOS / iPadOS 14 so you can see this demo.</p>
`
} else if (iOSVersion === 14) {
errorMessageWrapper.innerHTML = `
<p>In order to see WebGL2 content, you need to enable it from your device settings.</p>
<p>Settings > Safari > Advanced > Experimental Features > WebGL2.0</p>
`
}
} else {
errorMessageWrapper.innerHTML = `
<h1>Your browser does not support WebGL2</h1>
<p>Please try one of these alternative browsers:</p>
<ul>
<li>Microsoft Edge (version 79+)</li>
<li>Mozilla Firefox (version 51+)</li>
<li>Google Chrome (version 56+)</li>
<li>Opera (version 43+)</li>
</ul>
`
}
errorMessageWrapper.classList.add('webgl2-error')
document.body.appendChild(errorMessageWrapper)
}
/* ------- Generic helpers ------- */
function isMobileBrowser () {
return (function (a) {
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) {
return true
}
return false
})(navigator.userAgent || navigator.vendor || window.opera)
}
function isIOS () {
return (/AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)) || isIPadOS()
}
function isIPadOS () {
return navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 && !window.MSStream
}
function getIOSVersion () {
const found = navigator.userAgent.match(/(iPhone|iPad); (CPU iPhone|CPU) OS (\d+)_(\d+)(_(\d+))?\s+/)
if (!found || found.length < 4) {
return {
major: 0,
minor: 0
}
}
return {
major: parseInt(found[3], 10),
minor: parseInt(found[4], 10)
}
}
* { margin: 0; padding: 0; }
EDIT
By the first comment suggestion, I enabled the EXT_color_buffer_float and OES_texture_float_linear extensions and augmented my texImage2D call like this:
gl.getExtension('EXT_color_buffer_float')
gl.getExtension('OES_texture_float_linear')
// ...
const targetTexture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, targetTexture)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, targetTextureWidth, targetTextureHeight, 0, gl.RGBA, gl.FLOAT, null)
Unfortunately this results in corrupted rendering on all of my browsers running on MacOS. I updated my code snippet to reflect the new changes.
I read in MDN's Webgl2 best practices that EXT_float_blend is also required in order to allow blending in framebuffers, but is implicitly allowed when requesting EXT_color_buffer_float?
On a sidenote, I guess the OES_texture_float_linear would not work on mobile devices? So if the extension is not available, I would need to fallback to 8bit RGBA textures?
Thanks!
I'm pretty sure the issue the texture your rendering to is 8bits. Switch it to a floating point texture (RGBA32F) You'll need to check for and enable EXT_color_buffer_float and OES_texture_float_linear
Update
You say it won't work on mobile but you're using WebGL2 which hasn't shipped on iPhone yet (2021/1/3). As for RGBA32F not being renderable on mobile you could try RGBA16F. You'll have to check for and enable the corresponding extensions, EXT_color_buffer_half_float and OES_texture_half_float_linear. Your current code is not checking that the extensions actually exist (I'm assuming that was just to keep the code minimal)
The corruption is that your circle calculation draws alpha < 0 outside the circle but inside the quad. Before that was getting clipped to 0 because of the texture format but now with floating point textures it's not so it affects other circles.
Either discard if c <= 0 or clamp so it doesn't go below 0.
Note: you might find coloring faster and more flexible using a ramp texture. example, example2
Also note: It would have been nice if you'd created a more minimal repo. There's no need for the animation to show either issue
Update 2
Something else to point out, maybe you already knew this, but, the circle calculation
float dist = distance(v_uv, vec2(0.5));
float c = 0.5 - dist;
means the max value is 0.5 so half the range is being thrown away.
Switching to this
float dist = distance(v_uv, vec2(0.5)) * 2.0;
float c = 1.0 - dist;
Changes the range from 0 to 1 which is arguably better for shading later and to not throw away a bunch of precision in the texture. Example

Is the Sharpness filter available in Konvajs, if it is there how to use that?

https://konvajs.org/api/Konva.Filters.html
in this link the sharpness filter is not available
Konva doesn't have such a filter in its core. You have to implement it manually.
For that use case, you can write your own custom filter. See custom filters docs.
I tried to use that sharpen implementation: https://gist.github.com/mikecao/65d9fc92dc7197cb8a7c
// noprotect
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
function Sharpen(srcData) {
const mix = 1;
const w = srcData.width;
const h = srcData.height;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
var x, sx, sy, r, g, b, a, dstOff, srcOff, wt, cx, cy, scy, scx,
weights = [0, -1, 0, -1, 5, -1, 0, -1, 0],
katet = Math.round(Math.sqrt(weights.length)),
half = (katet * 0.5) | 0,
dstData = ctx.createImageData(w, h),
dstBuff = dstData.data,
srcBuff = srcData.data,
y = h;
while (y--) {
x = w;
while (x--) {
sy = y;
sx = x;
dstOff = (y * w + x) * 4;
r = 0;
g = 0;
b = 0;
a = 0;
for (cy = 0; cy < katet; cy++) {
for (cx = 0; cx < katet; cx++) {
scy = sy + cy - half;
scx = sx + cx - half;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
srcOff = (scy * w + scx) * 4;
wt = weights[cy * katet + cx];
r += srcBuff[srcOff] * wt;
g += srcBuff[srcOff + 1] * wt;
b += srcBuff[srcOff + 2] * wt;
a += srcBuff[srcOff + 3] * wt;
}
}
}
dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix);
dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
}
}
for(var i = 0; i < dstData.data.length; i++) {
srcData.data[i] = dstData.data[i];
}
}
Konva.Image.fromURL('https://i.imgur.com/ktWThtZ.png', img => {
img.setAttrs({filters: [Sharpen]});
img.cache();
layer.add(img);
layer.draw();
});
Demo: https://jsbin.com/tejalusano/1/edit?html,js,output

WebGL High GPU Usage

I am drawing thousand of colored quads by using WebGL (no any framework) and on my laptop, around 80k quads moves nicely in 60fps but more than 80K quads, fps starts waving regularly. Like a few frame 30fp, one frame 60 fps. When i check it Chrome's performance tools, i noticed that GPU is taking too much time.
This is how Chrome Performance tool look like when i run 100k quads
This is my example with no moving quads. Dynamic one also has same effect but STATIC one shows my problem better since no JS overhead.
My code here:
var objects = [];
var MAX_COUNT = 10000;
var projectionMatrix;
var gl;
var positionVertexBuffer;
var colorVertexBuffer;
var indicesBuffer;
{
gl = document.getElementById("renderCanvas").getContext("webgl", {preserveDrawingBuffer: false});
gl.disable(gl.STENCIL_TEST);
gl.disable(gl.DEPTH_TEST);
document.getElementById("renderCanvas").onclick = createObjects;
createObjects();
requestAnimationFrame(updateScreen);
}
function createObjects () {
projectionMatrix = new Float32Array([
0.0033333333333333335,0,0,
0,-0.0033333333333333335,0,
0,0,1
]);
var rObject = {};
rObject.projectionMatrix = projectionMatrix;
createPrograms(rObject);
createAttributes(rObject);
createMoveObjects(rObject);
rObject.id = "id_" + objects.length ;
objects.push(rObject);
}
function createMoveObjects (outObject) {
outObject.points = [];
var k = 0;
for (var i = 0; i < MAX_COUNT; i++) {
var x = (Math.random() * 600) - 300;
var y = (Math.random() * 600) - 300;
var vx = (Math.random() * 10) - 5;
var vy = (Math.random() * 10) - 5;
var size = 30 + Math.random() * 1;
var w = 26 / 2;
var h = 37 / 2;
var p = {w:w, h:h, x:x, y:y, vx:vx, vy:vy, size:size};
outObject.points.push(p);
}
}
var shaderProgram;
function createPrograms(outObject) {
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, document.getElementById("vertexShader").textContent );
gl.compileShader(vertexShader);
if ( !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) ) {
let finfo = gl.getShaderInfoLog( vertexShader );
console.log("Vertex Shader Fail" , finfo);
}
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, document.getElementById("fragmentShader").textContent);
gl.compileShader(fragmentShader);
if ( !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) ) {
let finfo = gl.getShaderInfoLog( fragmentShader );
console.log("Fragment Shader Fail" , finfo);
}
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
var pmlocation = gl.getUniformLocation(shaderProgram,"projectionMatrix");
gl.useProgram(shaderProgram);
gl.uniformMatrix3fv(pmlocation, false , outObject.projectionMatrix);
outObject.projectionMatrixLocation = pmlocation;
outObject.shaderProgram = shaderProgram;
}
function createAttributes(outObject) {
var vertices = new Float32Array(MAX_COUNT * 8);
var colors = new Float32Array(MAX_COUNT * 12);
var indices = new Uint16Array(6 * MAX_COUNT);
var index = 0;
for (var i = 0; i < indices.length; i+=6) {
indices[i ] = index;
indices[i + 1] = index + 1;
indices[i + 2] = index + 2;
indices[i + 3] = index + 1;
indices[i + 4] = index + 3;
indices[i + 5] = index + 2;
index += 4;
}
var r,g,b;
for (var i = 0; i < colors.length; i+=12) {
r = Math.random();
g = Math.random();
b = Math.random();
colors[i] = r;
colors[i + 1] = g;
colors[i + 2] = b;
colors[i + 3] = r;
colors[i + 4] = g;
colors[i + 5] = b;
colors[i + 6] = r;
colors[i + 7] = g;
colors[i + 8] = b;
colors[i + 9] = r;
colors[i + 10] = g;
colors[i + 11] = b;
}
var k = 0;
var w = 26 / 2;
var h = 37 / 2;
var x,y;
for (var i = 0; i < vertices.length; i++) {
x = (Math.random() * 600) - 300;
y = (Math.random() * 600) - 300;
vertices[k] = -w + x; vertices[k + 1] = h + y;
vertices[k + 2] = -w + x; vertices[k + 3] = -h + y;
vertices[k + 4] = w + x; vertices[k + 5] = h + y;
vertices[k + 6] = w + x; vertices[k + 7] = -h + y;
k +=8;
}
positionVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
positionVertexBuffer.location = gl.getAttribLocation(shaderProgram,"position");
gl.vertexAttribPointer(positionVertexBuffer.location,2 ,gl.FLOAT, false, 0,0);
gl.enableVertexAttribArray(positionVertexBuffer.location);
colorVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
colorVertexBuffer.location = gl.getAttribLocation(shaderProgram,"color");
gl.vertexAttribPointer(colorVertexBuffer.location,3 ,gl.FLOAT, false, 0,0);
gl.enableVertexAttribArray(colorVertexBuffer.location);
indicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
outObject.positionVertexBuffer = positionVertexBuffer;
outObject.colorVertexBuffer = colorVertexBuffer;
outObject.indicesBuffer = indicesBuffer;
outObject.vertices = vertices;
outObject.indices = indices;
outObject.colors = colors;
outObject.colorVertexLocation = colorVertexBuffer.location;
outObject.positionVertexLocation = positionVertexBuffer.location;
}
function updateAllPoints() {
var points;
var p;
for (var i = 0; i < objects.length; i++) {
points = objects[i].points;
var k = 0;
for (var j = 0; j < points.length; j++) {
p = points[j];
p.x += p.vx;
p.y += p.vy;
if(p.x >= 300){
p.x = 300;
p.vx *= -1;
} else if(p.x <= -300) {
p.x = -300;
p.vx *= -1;
} else if(p.y >= 300){
p.y = 300;
p.vy *= -1;
} else if(p.y <= -300) {
p.y = -300;
p.vy *= -1;
}
var vertices = objects[i].vertices;
vertices[k] = -p.w + p.x; vertices[k + 1] = p.h + p.y;
vertices[k + 2] = -p.w + p.x; vertices[k + 3] = -p.h + p.y;
vertices[k + 4] = p.w + p.x; vertices[k + 5] = p.h + p.y;
vertices[k + 6] = p.w + p.x; vertices[k + 7] = -p.h + p.y;
k +=8;
}
}
}
function renderScene() {
// updateAllPoints();
var totalDraw = 0;
gl.clearColor(0.3,0.3,0.3,1);
gl.clear(gl.COLOR_BUFFER_BIT);
var rO;
for (var i = 0; i < objects.length; i++) {
rO = objects[i];
drawObjects(rO);
totalDraw += MAX_COUNT;
}
document.getElementById("objectCounter").innerHTML = totalDraw + " Objects"
}
function drawObjects (rO) {
gl.useProgram(rO.shaderProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, rO.positionVertexBuffer);
// gl.bufferSubData(gl.ARRAY_BUFFER, 0, rO.vertices);
gl.vertexAttribPointer(rO.positionVertexLocation,2 ,gl.FLOAT, false, 0,0);
gl.bindBuffer(gl.ARRAY_BUFFER, rO.colorVertexBuffer);
gl.vertexAttribPointer(rO.colorVertexLocation,3 ,gl.FLOAT, false, 0,0);
gl.drawElements(gl.TRIANGLES,MAX_COUNT * 6 , gl.UNSIGNED_SHORT, 0);
}
function updateScreen() {
if(gl){
renderScene();
requestAnimationFrame(updateScreen);
}
}
<script id="vertexShader" type="x-shader/x-vertex">
uniform mat3 projectionMatrix;
attribute vec2 position;
attribute vec3 color;
varying vec3 colorData;
void main() {
colorData = color;
vec3 newPos = vec3(position.x, position.y, 1.0 ) * projectionMatrix;
gl_Position = vec4(newPos , 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision lowp float;
uniform sampler2D uSampler;
varying vec3 colorData;
void main() {
gl_FragColor = vec4(colorData, 1.0);
}
</script>
<canvas id="renderCanvas" width="200" height="200"></canvas>
<div id="objectCounter">10000 Objects</div>
<div>Evevy Click adds 10K Squares </div>
I also checked other examples and found PixiJS's Bunnymark test where you can run 120k bunnies in 60fps but no GPU overhead.
When comparing Bunnymark test, my GPU is taking too much time and I don't know why. I opimized it (of I think I did) but problem insists.
It turned out it is because of i left antialias default as context attributes which seems "true". Can't believe i did not notice.
This code worked for me
canvasDom.getContext("webgl", {antialias : false});
Use scene.remove(mesh) OR mesh.parent.remove(mesh)

arrow of line chart highcharts

I need to draw arrow of my line chart but i don't know well, here there are my code that make arrow http://jsfiddle.net/VQyVs/ I have probleme with serie 2
var lineSeries = Highcharts.seriesTypes.line;
var lineDrawGraph = lineSeries.prototype.drawGraph;
lineSeries.prototype.drawGraph = function() {
var arrowLength = 15,
arrowWidth = 9,
series = this,
segments = series.linedata || series.segments,
lastSeg = segments[segments.length - 1],
lastPoint = lastSeg[lastSeg.length - 1],
nextLastPoint = lastSeg[lastSeg.length - 2],
angle = Math.atan((lastPoint.plotX - nextLastPoint.plotX) /
(lastPoint.plotY - nextLastPoint.plotY)),
path = [];
angle = Math.PI+angle;
lineDrawGraph.apply(series, arguments);
path.push('M', lastPoint.plotX, lastPoint.plotY);
path.push(
'L',
lastPoint.plotX + arrowWidth * Math.cos(angle),
lastPoint.plotY - arrowWidth * Math.sin(angle)
);
path.push(
lastPoint.plotX + arrowLength * Math.sin(angle),
lastPoint.plotY + arrowLength * Math.cos(angle)
);
path.push(
lastPoint.plotX - arrowWidth * Math.cos(angle),
lastPoint.plotY + arrowWidth * Math.sin(angle),
'Z'
);
series.chart.renderer.path(path)
.attr({
fill: series.color
})
.add(series.group);
};
Can any one help me? Thanks
Series 2 is not sorted properly. It's putting the arrow head on the last point in the array which happens to be the first point on the X axis.
{
data: [[0.10391336,-0.647706317],
[0.208684058,-0.439022259],
[0.031920245,-0.407102014],
[-0.280249839,-0.687351853]].sort(), // I added the .sort...
marker: {
enabled: false
}
}
UPDATE
I think I understand what you are after now. To reverse the direction of the head, you'll have to test for the direction (is it moving to the left or right) and then modify how it's drawn:
if (lastPoint.plotX > nextLastPoint.plotX)
{
// to the right
path.push(
'L',
lastPoint.plotX + arrowWidth * Math.cos(angle),
lastPoint.plotY - arrowWidth * Math.sin(angle)
);
path.push(
lastPoint.plotX + arrowLength * Math.sin(angle),
lastPoint.plotY + arrowLength * Math.cos(angle)
);
path.push(
lastPoint.plotX - arrowWidth * Math.cos(angle),
lastPoint.plotY + arrowWidth * Math.sin(angle),
'Z'
);
}
else
{
// to the left
path.push(
'L',
lastPoint.plotX - arrowWidth * Math.cos(angle),
lastPoint.plotY + arrowWidth * Math.sin(angle)
);
path.push(
lastPoint.plotX - arrowLength * Math.sin(angle),
lastPoint.plotY - arrowLength * Math.cos(angle)
);
path.push(
lastPoint.plotX + arrowWidth * Math.cos(angle),
lastPoint.plotY - arrowWidth * Math.sin(angle),
'Z'
);
}
See new fiddle.
The problem is with the overriding of the original value for angle:
angle = Math.PI+angle;
Should be:
if (lastPoint.plotX > nextLastPoint.plotX){
if (angle < 0) angle = Math.PI + angle;
}
else{
if (angle > 0) angle = Math.PI + angle;
}
Mark's point about the direction of the line was correct but the proposed solution did not always work depending on the slope of the last two points of the line.
See fiddle

Trouble Rendering a Sphere in WebGL and Typescript

Ive ported over some c code that renders a sphere in opengl for a webgl/typescript project I'm working on, however its not rendering correctly. I've compared the indices and vertices between the c and ts versions and they appear to match. The code is as follows:
constructor(ctx: WebGLRenderingContext, stacks:number,
slices:number, scale: number){
var vertices: number[] = [];
var normals: number[] = [];
var indices: number[] = [];
var ii: number;
var jj: number;
var v: number;
var u: number;
normals.push(0, 0, 1);
vertices.push(0, 0, scale);
for (ii = 0; ii < slices; ++ii) {
indices.push(0);
indices.push(ii + 1);
}
indices.push(0);
indices.push(1);
for (ii = 1; ii < stacks; ++ii) {
v = ii / stacks;
for (jj = 0; jj < slices; ++jj) {
u = jj / slices;
normals.push.apply(normals, this.shapeNormal(u, v));
vertices.push.apply(vertices, this.shapeVertex(scale, u, v));
indices.push((ii - 1) * slices + (jj + 1));
var index_offset: number = ((ii + 1) === stacks) ? 0 : jj;
var second: number = ii * slices + (index_offset + 1);
//console.log("Offset: " + String(index_offset) + " Value: " + String(second));
indices.push(second);
}
indices.push((ii - 1) * slices + 1);
indices.push(ii * slices + 1);
}
normals.push(0, 0, -1);
vertices.push(0, 0, -scale);
//console.log("Theoretical vertices: " + String(3 * (2 + slices * (stacks - 1))));
//initialise vbos
console.log("Vertices: " + String(vertices.length / 3));
for(var l = 0; l < vertices.length; l += 3)
console.log(vertices[l].toFixed(6) + " " + vertices[l+1].toFixed(6) + " " + vertices[l+2].toFixed(6));
this.vertices = new VertexBufferObject(ctx, 3, vertices.length / 3);
//console.log("Normals: " + String(normals.length));
this.normals = new VertexBufferObject(ctx, 3, normals.length / 3);
console.log("Indices: " + String(indices.length) + " " + indices.toString());
this.indices = new VertexBufferObject(ctx, 1, indices.length);
//populate vbo
ctx.enableVertexAttribArray(0);
ctx.bindBuffer(ctx.ARRAY_BUFFER, this.vertices.buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(vertices), ctx.STATIC_DRAW);
ctx.enableVertexAttribArray(1);
ctx.bindBuffer(ctx.ARRAY_BUFFER, this.normals.buffer);
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(normals), ctx.STATIC_DRAW);
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, this.indices.buffer);
ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices),
ctx.STATIC_DRAW);
ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
ctx.disableVertexAttribArray(0);
ctx.disableVertexAttribArray(1);
this.ctx = ctx;
}
private shapeVertex(r: number, u: number, v: number): number[] {
/* Use maths rather than physics spherical coordinate convention */
var theta: number = u * 2.0 * Math.PI;
var phi: number = v * Math.PI;
var vert: number[] = [
r * Math.cos(theta) * Math.sin(phi),
r * Math.sin(theta) * Math.sin(phi),
r * Math.cos(phi)
];
return vert;
}
private shapeNormal(u: number, v: number): number[] {
/* Use maths rather than physics spherical coordinate convention */
var theta: number = u * 2.0 * Math.PI;
var phi: number = v * Math.PI;
var norm: number[] = [
Math.cos(theta) * Math.sin(phi),
Math.sin(theta) * Math.sin(phi),
Math.cos(phi)
];
var mag: number = Math.sqrt(norm[0] * norm[0] + norm[1] * norm[1] + norm[2] * norm[2]);
norm[0] /= mag;
norm[1] /= mag;
norm[2] /= mag;
return norm;
}
public draw(shaderProgram: ShaderProgram): void {
//bind and draw vbo's
this.ctx.enableVertexAttribArray(0);
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.vertices.buffer);
this.ctx.vertexAttribPointer(shaderProgram.attributes.position,
this.vertices.itemSize, this.ctx.FLOAT, false, 0, 0);
this.ctx.enableVertexAttribArray(1);
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.normals.buffer);
this.ctx.vertexAttribPointer(shaderProgram.attributes.normal,
this.normals.itemSize, this.ctx.FLOAT, false, 0, 0);
this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, this.indices.buffer);
this.ctx.drawElements(this.ctx.TRIANGLES, this.indices.numItems,
this.ctx.UNSIGNED_SHORT, 0);
this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, null);
this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, null);
this.ctx.disableVertexAttribArray(0);
this.ctx.disableVertexAttribArray(1);
}
and a screenshot of the result:
Broken Sphere
Thank you in advance
As TypeScript is just a supersed of Javascript, your problem is probably related to how Javascript handle your code computations.
I'm not sure about your code as you didn't provide the original source.
Assuming your code is correct, you may encounter a floating point approximation error.

Resources