I am writing a fragment shader for an image processing algorithm. The shader will run multiple times between two framebuffers in a loop (ping-pong). At some point I need to stop the loop when input and output textures are identical.
What I intend to do is the last step of Canny edge detector algorithm, "hysterezis edge tracking". I want to make a real time GPU/WebGL2 version of Canny algorithm and upload it to a web site.
This last step is as follows:
Given a double thresholded image containing "strong" edge pixels (1.0) and "weak" edge pixels (0.5)
find all chains of weak pixels connected with a strong pixel and mark them "strong"
keep all "strong" pixels and discard all remaining "weak" ones.
This can be implemented in a fragment shader running multiple times in a loop. The current "weak" pixel is marked "strong" if there is at least one strong pixel in its 8-pixel neighbourhood. At every iteration, we should have more strong pixels and less weak pixels. At the end, only isolated chains of weak pixel should remain. This is the point where the fragment shader becomes a pass-through shader and should be detected to stop the loop.
Update Sept 2019: I uploaded the GPU Canny Edge Detector here http://www.oldrinb.info/dip/canny/ . It works in browsers with WebGL2 support, as well in browsers that support WebGL1 and 'WEBGL_draw_buffers' extension. I'll put also the source code to github shortly.
I'm not 100% sure what you're asking. You're asking to compare on the CPU. You can read the contents of a texture by attaching it to a framebuffer and then calling gl.readPixels. you can then compare all the pixels. Note: not all texture formats can be attached to a framebuffer but assuming you're using a format that can. You've already attached textures to framebuffers for your ping-ponging so what more did you want?
Like I wrote in the comment on the GPU you can write a shader to compare 2 textures
#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
now just draw 1 pixel and read it with readPixels. if it's 0 the textures are the same. If it's not they are different.
The code assumes the textures are the same size but of course if they aren't the same size then we already know they can't be the same.
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
outColor = mix(vec4(0), vec4(1), step(len, 0.0));
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
compareTextures(0, 1);
compareTextures(1, 2);
function compareTextures(ndx1, ndx2) {
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
// read the pixel
const result = new Uint8Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log('textures', ndx1, 'and', ndx2, 'are', result[0] ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
You could also use occlusion queries. The plus is they might not block the GPU where as readPixels does. The minus is you can't check them in the same JavaScript event so they might not fit your needs
// make 3 canvaes as sources for textures
const canvases = ['A', 'B', 'B'].map((msg) => {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 128, 128);
ctx.font = '80px monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'yellow';
ctx.fillText(msg, 64, 64);
document.body.appendChild(canvas);
return canvas;
});
const gl = document.createElement('canvas').getContext('webgl2');
if (!gl) { alert('need webgl2'); }
const vs = `#version 300 es
void main() {
gl_PointSize = 1.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
const fs = `#version 300 es
precision highp float;
uniform sampler2D tex1;
uniform sampler2D tex2;
out vec4 outColor;
void main() {
ivec2 size = textureSize(tex1, 0); // size of mip 0
float len = 0.0;
for (int y = 0; y < size.y; ++y) {
for (int x = 0; x < size.x; ++x) {
vec4 color1 = texelFetch(tex1, ivec2(x, y), 0);
vec4 color2 = texelFetch(tex2, ivec2(x, y), 0);
vec4 diff = color1 - color2;
len = length(diff);
if (len > 0.0) break;
}
if (len > 0.0) break;
}
if (len > 0.0) {
discard;
}
outColor = vec4(1);
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const textures = canvases.map((canvas) => {
// gl.createTexture, gl.bindTexture, gl.texImage, etc.
return twgl.createTexture(gl, {src: canvas});
});
function wait(ms = 0) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function test() {
await compareTextures(0, 1);
await compareTextures(1, 2);
}
test();
async function compareTextures(ndx1, ndx2) {
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.useProgram(programInfo.program);
// gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
tex1: textures[ndx1],
tex2: textures[ndx2],
});
// draw the bottom right pixel
gl.viewport(0, 0, 1, 1);
const query = gl.createQuery();
gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
gl.endQuery(gl.ANY_SAMPLES_PASSED);
gl.flush();
let ready = false;
while(!ready) {
await wait();
ready = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
}
const same = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('textures', ndx1, 'and', ndx2, 'are', same ? 'the same' : 'not the same');
}
canvas { padding: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>
Related
I'm trying to render a tile map onto a single quad.
My approach uses a "tile map" texture in which each pixel stores the X and Y indices of a tile inside a tile set.
When rendering a fragment, the idea is to:
Sample the "tile map" texture using vertex texture coordinates
Retrieve the X and Y indices from the R and G channels of the texture
Calculate the UVs of the selected tile
Use the UVs to sample the texture atlas
I'm having issues with getting #3 to work.
Here is the shader code I'm trying to use to render this:
vertex
#version 300 es
precision mediump float;
uniform mat4 uVIEW;
uniform mat4 uPROJECTION;
uniform mat3 uMODEL;
layout(location = 0) in vec2 aPOSITION;
layout(location = 1) in vec2 aTEXCOORD;
out vec2 vTEXCOORD;
void main()
{
// flip uv and pass it to fragment shader
vTEXCOORD = vec2(aTEXCOORD.x, 1.0f - aTEXCOORD.y);
// transform vertex position
vec3 transformed = uMODEL * vec3(aPOSITION, 1.0);
gl_Position = uPROJECTION * uVIEW * vec4(transformed.xy, 0.0, 1.0);
}
fragment
#version 300 es
precision mediump float;
precision mediump usampler2D;
uniform usampler2D uMAP;
uniform sampler2D uATLAS;
uniform vec2 uATLAS_SIZE;
in vec2 vTEXCOORD;
out vec4 oFRAG;
void main()
{
// sample "tile map" texture
vec4 data = vec4(texture(uMAP, vTEXCOORD));
// calculate UV
vec2 uv = (data.xy * 32.0 / uATLAS_SIZE) + (vTEXCOORD * 32.0 / uATLAS_SIZE);
// sample the tileset
oFRAG = texture(uATLAS, uv);
}
I believe this is the culprit:
vec2 uv = (data.xy * 32.0 / uATLAS_SIZE) + (vTEXCOORD * 32.0 / uATLAS_SIZE);
The formula here is uv = (tile_xy_indices * tile_size) + (texcoord * tile_size), where:
texcoord is the vertex uv (the standard [0, 1], [0, 0], [1, 0], [1, 1])
tile_xy_indices are the X,Y coordinates of the tile in the tileset
tile_size is the normalized size of one tile in the tileset
So if I have the values texcoord = (0, 0), tile_xy_indices = (7, 7), tile_size = (32 / 1024, 32 / 1024), then the UV for this fragment should be (0.21875, 0.21875), and if texcoord = (1, 1), then it should be (0.25, 0.25). These values seem correct to me, why do they produce the wrong result, and how do I fix it?
Here is some extra context:
Tile map texture (exaggerated colors):
Expected result (minus the grid lines):
Actual result:
The code is conflating 3 things in these 2 lines
// sample "tile map" texture
vec4 data = vec4(texture(uMAP, vTEXCOORD));
// calculate UV
vec2 uv = (data.xy * 32.0 / uATLAS_SIZE) + (vTEXCOORD * 32.0 / uATLAS_SIZE);
// sample the tileset
The first one, for the entire quad you're drawing, goes over your entire tilemap. That's probably not what you want. Usually apps that use a tilemap want to show a portion of it, not the entire thing.
The second problem is the second line needs to know how many pixels a tile will cover, not how many pixels a tile is. In other words if you have a 32x32 tile and you draw it in 4x4 pixels then your texture coordinate need to go from 0.0 to 1.0 across that tile in 4 pixels, not in 32 pixels.
A 3rd problem is dividing by 32 will not go across a 32pixel tile unless there are 32 tiles across the texture. Imagine you have a single tile is 32x32 pixels but there 8x4 tiles in your tile set. You need go from 0 to 1 across 1/8th and 1/4th not across 1/32
this answer implements a tilemap
Effectively it uses 2 matrices. One to draw the quad which being a quad could be rotated, scaled, projected in 3D etc, but lets just say the normal thing for a tilemap would be to just draw a quad that covers the canvas.
The second is a texture matrix (or tile matrix) where each unit is 1 tile. So given, a 0 to 1 quad you compute a matrix to expand and rotate that quad on to the quad above.
Let's say you don't rotate, you still need to decide how many tiles to draw across and down the quad. If you wanted 4 tiles across the quad and 3 tiles down then you you'd set the scale to x=4 and y=3.
This way, automatically, every tile goes from 0 to 1 in its own space. Or maybe to put it another way, tile 2x7 goes from 2.0<->3.0 in U and 7.0<->8.0 in V. We can then look up from the map tile 2,7 and use fract to cover that tile in the space that tile occupies in the quad.
const vs = `#version 300 es
precision mediump float;
uniform mat4 uVIEW;
uniform mat4 uPROJECTION;
uniform mat4 uMODEL;
uniform mat4 uTEXMATRIX;
layout(location = 0) in vec4 aPOSITION;
layout(location = 1) in vec4 aTEXCOORD;
out vec2 vTEXCOORD;
void main()
{
vTEXCOORD = (uTEXMATRIX * aTEXCOORD).xy;
gl_Position = uPROJECTION * uVIEW * uMODEL * aPOSITION;
}
`;
const fs = `#version 300 es
precision mediump float;
precision mediump usampler2D;
uniform usampler2D uMAP;
uniform sampler2D uATLAS;
uniform vec2 uTILESET_SIZE; // how many tiles across and down the tileset
in vec2 vTEXCOORD;
out vec4 oFRAG;
void main()
{
// the integer portion of vTEXCOORD is the tilemap coord
ivec2 mapCoord = ivec2(vTEXCOORD);
uvec4 data = texelFetch(uMAP, mapCoord, 0);
// the fractional portion of vTEXCOORD is the UV across the tile
vec2 texcoord = fract(vTEXCOORD);
vec2 uv = (vec2(data.xy) + texcoord) / uTILESET_SIZE;
// sample the tileset
oFRAG = texture(uATLAS, uv);
}
`;
const tileWidth = 32;
const tileHeight = 32;
const tilesAcross = 8;
const tilesDown = 4;
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) alert('need WebGL2');
// compile shaders, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// gl.createBuffer, bindBuffer, bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
aPOSITION: {
numComponents: 2,
data: [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
],
},
aTEXCOORD: {
numComponents: 2,
data: [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
],
},
});
function r(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + (max - min) * Math.random();
}
// make some tiles
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = tileWidth * tilesAcross;
ctx.canvas.height = tileHeight * tilesDown;
ctx.font = "bold 24px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const f = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~';
for (let y = 0; y < tilesDown; ++y) {
for (let x = 0; x < tilesAcross; ++x) {
const color = `hsl(${r(360) | 0},${r(50,100)}%,50%)`;
ctx.fillStyle = color;
const tx = x * tileWidth;
const ty = y * tileHeight;
ctx.fillRect(tx, ty, tileWidth, tileHeight);
ctx.fillStyle = "#FFF";
ctx.fillText(f.substr(y * 8 + x, 1), tx + tileWidth * .5, ty + tileHeight * .5);
}
}
document.body.appendChild(ctx.canvas);
const tileTexture = twgl.createTexture(gl, {
src: ctx.canvas,
minMag: gl.NEAREST,
});
// make a tilemap
const mapWidth = 400;
const mapHeight = 300;
const tilemap = new Uint32Array(mapWidth * mapHeight);
const tilemapU8 = new Uint8Array(tilemap.buffer);
const totalTiles = tilesAcross * tilesDown;
for (let i = 0; i < tilemap.length; ++i) {
const off = i * 4;
// mostly tile 9
const tileId = r(4) < 1
? (r(totalTiles) | 0)
: 9;
tilemapU8[off + 0] = tileId % tilesAcross;
tilemapU8[off + 1] = tileId / tilesAcross | 0;
}
const mapTexture = twgl.createTexture(gl, {
internalFormat: gl.RGBA8UI,
src: tilemapU8,
width: mapWidth,
minMag: gl.NEAREST,
});
function ease(t) {
return Math.cos(t) * .5 + .5;
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function easeLerp(a, b, t) {
return lerp(a, b, ease(t));
}
function render(time) {
time *= 0.001; // convert to seconds;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 1, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// these mats affects where the quad is drawn
const projection = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
const view = m4.identity();
const model =
m4.scaling([gl.canvas.width, gl.canvas.height, 1]);
const tilesAcrossQuad = 10;//easeLerp(.5, 2, time * 1.1);
const tilesDownQuad = 5;//easeLerp(.5, 2, time * 1.1);
// scroll position in tiles
// set this to 0,0 and the top left corner of the quad
// will be the start of the map.
const scrollX = time % mapWidth;
const scrollY = 0;//time % (mapHeight * tileHeight);
const tmat = m4.identity();
// sets where in the map to look at in tile coordinates
// so 3,4 means start drawing 3 tiles over, 4 tiles down
m4.translate(tmat, [scrollX, scrollY, 0], tmat);
// sets how many tiles to display
m4.scale(tmat, [tilesAcrossQuad, tilesDownQuad, 1], tmat);
twgl.setUniforms(programInfo, {
uPROJECTION: projection,
uVIEW: view,
uMODEL: model,
uTEXMATRIX: tmat,
uMAP: mapTexture,
uATLAS: tileTexture,
uTILESET_SIZE: [tilesAcross, tilesDown],
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
next time post a working snippet is so much friendlier to answerers.
I am under the impression that WebGL is much more powerful than the 2d renderer in the browser but for some reason, my WebGL code runs much slower. Are there any recommended optimization strategies I can use to make my WebGL code run a little bit faster?
Are there any code in my rect function that should be left out because I am new to WebGL and most tutorials don't cover how to make a rect function.
WebGL Code
const canvas = document.getElementById("canvas");
const vertexCode = `
precision mediump float;
attribute vec4 position;
uniform mat4 matrix;
uniform vec4 color;
varying vec4 col;
void main() {
col = color;
gl_Position = matrix * position;
}
`;
const fragmentCode = `
precision mediump float;
varying vec4 col;
void main() {
gl_FragColor = col;
}
`;
const width = canvas.width;
const height = canvas.height;
const gl = canvas.getContext("webgl");
if(!gl) {
console.log("WebGL not supported");
}
const projectionMatrix = [
2/width, 0, 0, 0,
0, -2/height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
function rect(x, y, w, h) {
const vertex = [
x, y, 0, 1,
x+w, y, 0, 1,
x, y+h, 0, 1,
x+w, y+h, 0, 1
]
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);
const projectionLocation = gl.getUniformLocation(program, `matrix`);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function fill(r, g, b, a) {
const projectionLocation = gl.getUniformLocation(program, `color`);
gl.uniform4fv(projectionLocation, [r, g, b, a]);
}
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
requestAnimationFrame(animate);
for(let i=0;i<200;i++) {
fill(1, 0, 0, 1);
rect(random(0, 800), random(0, 600), 10, 10);
}
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
Using the normal 2D renderer
const canvas = document.getElementById("canvas");
const c = canvas.getContext("2d");
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
requestAnimationFrame(animate);
c.fillStyle = "black";
c.fillRect(0, 0, 800, 600);
c.fillStyle = "red";
for(let i=0;i<200;i++) {
c.fillRect(random(0, 800), random(0, 600), 10, 10);
}
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
There are 1000s of ways to optimized WebGL. Which one you choose depends on your needs. The more you optimize generally the less flexible.
First you should do the obvious and not create new buffers for every rectangle as your code is doing now and also move anything outside the rendering that can be moved outside (like looking up locations) which is shown in the answer by Józef Podlecki.
Another is you could move and scale the rectangle use the uniform matrix rather than uploading new vertices for each rectangle. It's unclear whether or not updating the matrix would be faster or slower for rectangles but if you were drawing something with more vertices it would definitely be faster do it by matrix. This series of articles mentions that and builds up to matrices.
Another is you can use vertex arrays though that's not important for your example since you're only drawing a single thing.
Another is if the shapes are all the same (as yours are) you can use instanced drawing to draw all 200 rectangles with one draw call
If the shapes are not all the same you can use creative techniques like storing their data in textures.
Another is you can put more than one shape in a buffer. So for example instead of putting one rectangle in the buffer put all 200 rectangles in the buffer and then draw them with one draw call. From this presentation
Also note: the callback to requestAnimationFrame is passed the time since the page loaded so there is no need to create Date objects. Creating Date objects is slower than not and the time passed to requestAnimationFrame is more accurate than Date
let lastTime = 0;
function animate(currentTime) {
console.log(1000 / (currentTime - lastTime));
lastTime = currentTime;
...
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
It's also slow to print to the console. You'd be better off updating an element's content
let lastTime = 0;
function animate(currentTime) {
someElement.textContent = (1000 / (currentTime - lastTime)).toFixed(1);
lastTime = currentTime;
...
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
You can reuse buffer, extract getAttribLocation,getUniformLocation outside of rect function as well as vertexAttribPointer once you bound buffer and lastly call requestAnimationFrame after rendering
const canvas = document.getElementById("canvas");
const vertexCode = `
precision mediump float;
attribute vec4 position;
uniform mat4 matrix;
uniform vec4 color;
varying vec4 col;
void main() {
col = color;
gl_Position = matrix * position;
}
`;
const fragmentCode = `
precision mediump float;
varying vec4 col;
void main() {
gl_FragColor = col;
}
`;
const width = canvas.width;
const height = canvas.height;
const gl = canvas.getContext("webgl");
if(!gl) {
console.log("WebGL not supported");
}
const projectionMatrix = [
2/width, 0, 0, 0,
0, -2/height, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexCode);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentCode);
gl.compileShader(fragmentShader);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
const positionBuffer = gl.createBuffer();
const positionLocation = gl.getAttribLocation(program, "position");
const projectionLocation = gl.getUniformLocation(program, `matrix`);
const projectionColorLocation = gl.getUniformLocation(program, `color`);
gl.enableVertexAttribArray(positionLocation);
const vertex = [
0, 0, 0, 1,
0, 0, 0, 1,
0, 0, 0, 1,
0, 0, 0, 1
]
const floatArray = new Float32Array(vertex)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 4, gl.FLOAT, false, 0, 0);
gl.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
function rect(x, y, w, h) {
floatArray[0] = x;
floatArray[1] = y;
floatArray[4] = x + w;
floatArray[5] = y;
floatArray[8] = x;
floatArray[9] = y + h;
floatArray[12] = x + w;
floatArray[13] = y + h;
gl.bufferData(gl.ARRAY_BUFFER, floatArray, gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function fill(r, g, b, a) {
gl.uniform4fv(projectionColorLocation, [r, g, b, a]);
}
let lastTime = new Date();
function animate() {
let currentTime = new Date();
console.log(1000 / (currentTime.getTime() - lastTime.getTime()));
lastTime = new Date();
for(let i=0;i<200;i++) {
fill(1, 0, 0, 1);
rect(random(0, 800), random(0, 600), 10, 10);
}
requestAnimationFrame(animate);
}
animate();
function random(low, high) {
return low + Math.random() * (high-low)
}
<canvas id="canvas" width="500" height="300"></canvas>
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I tried to study WebGL, but I have nothing.
I do not want to use the library, they are too big.
I wrote what I want on the canvas, help me to do it on WebGL
https://jsfiddle.net/g9tx903c/
<canvas id="canvas" width="500" height="200"></canvas>
<script type='text/javascript'>
function DrawPoint(x, y, size, blur, opacity) {
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var halfSize = size / 2;
var radgrad = ctx.createRadialGradient(halfSize + x, halfSize + y, 0, halfSize + x, halfSize + y, halfSize);
radgrad.addColorStop(blur, 'rgba(255, 255, 255, ' + opacity +')');
radgrad.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = radgrad;
ctx.fillRect(x, y, size, size);
}
function DrawcGradient() {
var w = 500;
var h = 200;
var pointR = w / 2;
var x = (w / 2);
var y = (h / 2);
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
var grd = ctx.createRadialGradient(x, y, 0, x, y, pointR);
grd.addColorStop(0, '#5A6977');
grd.addColorStop(1, '#000');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, w, h);
}
DrawcGradient();
DrawPoint(50, 100, 50, 0.5, 0.2);
DrawPoint(70, 10, 150, 0.93, 0.2);
</script>
This just happens to fall into a domain I am catching up on again. Maybe not quite what you want but you can remove all the 2D canvas stuff and just keep the webGL stuff.
This demo mixes 2D and 3D canvas interfaces to get high performance GPU processing on 2D canvas content.
The fragment shader does the work of creating the gradients near bottom of code called FragmentShader
Radial gradients are created by calculating the distance from the center and getting a value 0 to 1. I use this value to get the 3 gradients. To change the sharpness of the gradients I multiply, pull down and clamp the values.
eg the big circle near mouse is 1 at center and 0 away. I multiply by 350 to give 350 to 0. then I pull down by 270 to give 80 to -270, then I clamp to 0 and 1. The result is a sharp gradient.
I position the circles on a line from a point just at the top of the canvas and one near the mouse the other further along the line.
There is a lot of boiler plate stuff and I am a little over it today so you will have to nut out the rest.
Very easy to convert to all webGL. Just remove all the canvas mouse stuff up the top. Create an image and call startWebGL(image) Add webGL canvas (after you create it in startWebGL function) to the document body and call at least once the function webGLRender to set the shader variables and render a frame.
Warning the shaders have high level directive and can not be used without supporting code. To use with other setups remove the # infront of all uniform and attribute variables in shaders. Ie #uniform vec2 mouse; becomes uniform vec2 mouse;
//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;
const RESIZE_DEBOUNCE_TIME = 100;
var onresize; // demo use this to do your thing
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
createCanvas = function () { // create 2D display canvas
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) {
canvas = createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onresize === "function"){
resizeCount += 1;
setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);
}
}
function debounceResize(){
resizeCount -= 1;
if(resizeCount <= 0){
onresize();
}
}
setGlobals = function(){
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
mouse.updateBounds();
}
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
/** SimpleFullCanvasMouse.js end **/
function display(){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if(webGL !== undefined){
webGLRender();
}
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
// END of boilerplate stuff.
/* ***************************************************************************************************
The following functions are helpers for Shader variables. Rather than having to type all the mumbo
jumbo to locate shader variable and then store the location ID getVariables and getLocations does
it for you. uniform and attribute variable that are prefixed with # are can used via the gl.locs.name
(no space between # and variable type #attribute is good # attribute is bad)
For example
#uniform vec3 myVec; // the shader source code
Is located and given the name myVec in gl.locs and can be set in javascript
gl.uniform3f(gl.locs.myVec, 0.3, 0.5, 0.8); //
Please not that this makes shaders source code none standard and will not complie as is without these
functions . Just remove the #
*************************************************************************************************** */
const VAR_TYPES = ["attribute","uniform"];
const VAR_LOCATE_FUNC = {attribute : "getAttribLocation", uniform : "getUniformLocation"}
// get # delimited variables from shader source
function getVariables(script,types){
VAR_TYPES.forEach(f => {
if(types.items === undefined){ types.items = []; }
script = script.replace(new RegExp("#" + f+".+;","g"), str => {
var data = str.replace(/ /g," ").split(" ");
types.items.push({use : f , type : data[1] , name : data[2].replace(";","")});
return str.substr(1);
})
})
return script;
}
// get location IDs for shader variables
var getLocations = function(gl,shaders){
var locs = {};
shaders.variables.items.forEach(v => { locs[v.name] = gl[VAR_LOCATE_FUNC[v.use]](shaders.program, v.name); });
return locs;
}
/* end of var heplers ***************************************************************************** */
// creates vertex and fragment shaders
function createProgramFromScripts( gl, ids) {
var shaders = [];
var variables = {};
for (var i = 0; i < ids.length; i += 1) {
var script = shadersSource[ids[i]];
if (script !== undefined) {
var shader = gl.createShader(gl[script.type]);
var source = getVariables(script.source,variables)
gl.shaderSource(shader, source);
gl.compileShader(shader);
shaders.push(shader);
}else{
throw new ReferenceError("*** Error: unknown script ID : " + ids[i]);
}
}
var program = gl.createProgram();
shaders.forEach((shader) => { gl.attachShader(program, shader); });
gl.linkProgram(program);
gl.locs = getLocations(gl,{ program : program,variables : variables});
return program;
}
// setup simple 2D webGL image processor
var webGL;
function startWebGL(image) {
webGL = document.createElement("canvas");
webGL.width = image.width;
webGL.height = image.height;
var gl = webGL.gl = webGL.getContext("webgl");
var program = createProgramFromScripts(gl, ["VertexShader", "FragmentShader"]);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(gl.locs.texCoord);
gl.vertexAttribPointer(gl.locs.texCoord, 2, gl.FLOAT, false, 0, 0);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform2f(gl.locs.resolution, webGL.width, webGL.height);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.enableVertexAttribArray(gl.locs.position);
gl.vertexAttribPointer(gl.locs.position, 2, gl.FLOAT, false, 0, 0);
setRectangle(gl, 0, 0, image.width, image.height);
}
function setRectangle(gl, x, y, width, height) { // set draw rectangle
var x1 = x + width;
var y1 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ x, y, x1, y, x, y1, x, y1, x1, y, x1, y1]), gl.STATIC_DRAW);
}
var shadersSource = {
VertexShader : {
type : "VERTEX_SHADER",
source : `
// the # is a higher level directive to indicate that the variable needs to
// be loacted as value set or read
#attribute vec2 position;
#attribute vec2 texCoord;
#uniform vec2 resolution;
varying vec2 u_texCoord; // varying means this is moved to the frag shader
float aspect = resolution.x/resolution.y;
varying float aspect1;
void main() {
vec2 zeroToOne = position / resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
u_texCoord = vec2(texCoord.x ,texCoord.y / aspect);
aspect1 = aspect;
}`
},
FragmentShader : {
type : "FRAGMENT_SHADER",
source : `
// the # is a higher level directive to indicate that the variable needs to
// be loacted as value set or read
precision mediump float;
#uniform sampler2D u_image;
#uniform vec4 backDark; // the background dark colour
#uniform vec4 backLight; // backgroun light colour
#uniform vec4 ringCol; // rings colour
#uniform vec2 lightLoc; // location of point from which to project second ring
#uniform vec2 mouse; // location of big ring
varying float aspect1;
varying vec2 u_texCoord; // texture coord for all mapping and comes from the
// vertext shader. If you change the name here
// change it in the vert shader
float dist; // used for background gradient
vec4 pixelCol; // to hold the final pixel colour
vec2 gradCenter = vec2(0.5,0.25); // location of background gradient center
vec2 secondRing = lightLoc+ (mouse - lightLoc) * (1.2 + distance(mouse,lightLoc));
void main() {
pixelCol = texture2D(u_image, vec2(u_texCoord.x,u_texCoord.y * aspect1));
//pixelCol = texture2D(u_image, vec2(u_texCoord.x,u.texCoord.y );
// get distance from center of background gradient
dist = distance(gradCenter,u_texCoord) / 0.707;
// use dist to caculate the background gradient
pixelCol += (backDark - backLight) * dist + backLight;
// add the big ring colour to the background. mouse is the center of big ring
pixelCol += clamp((1.0-distance(mouse,u_texCoord)) * 345.0 - 270.5 ,0.0,1.0) * ringCol;
// add the second rign colour to the background colour. secondRing is location of second ring
pixelCol += clamp((1.0-distance(secondRing,u_texCoord)) * 29.0 - 27. ,0.0,1.0) * ringCol;
gl_FragColor = pixelCol; // set the fragment to the colour caculated
}`
}
}
var firstRun = true;
function webGLRender(){
var gl = webGL.gl;
if(firstRun ){
firstRun = false;
gl.uniform4f(gl.locs.backDark, 0,0,0,1)
gl.uniform4f(gl.locs.backLight, 0.3,0.5,0.8,1)
gl.uniform4f(gl.locs.ringCol, 0.1,0.1,0.38,0.41)
gl.uniform2f(gl.locs.lightLoc, 0.5,0.0);
}
gl.uniform2f(gl.locs.mouse, mouse.x / w,mouse.y / h);
gl.drawArrays(gl.TRIANGLES, 0, 6);
ctx.drawImage(webGL,0,0, canvas.width, canvas.height);
}
function createImageAndStartWebGL(){
var image = document.createElement("canvas");
image.width = canvas.width;
image.height = canvas.height;
image.ctx = image.getContext("2d");
image.ctx.fillRect(0,0,canvas.width,canvas.height);
image.ctx.font = "48px arial";
image.ctx.textAlign = "center";
image.ctx.fillStyle = "white";
image.ctx.fillText("WebGL & Canvas 2D demo",canvas.width/2,48);
image.ctx.font = "16px arial";
image.ctx.fillText("WebGL fragment shader processing 2D canvas image, then render back to 2D canvas.",canvas.width/2,66);
firstRun = true;
startWebGL(image);
}
// Add the setup to the debounced resize
onresize = createImageAndStartWebGL;
// start it all happening
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
requestAnimationFrame(update);
code:
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
Problem in that on a figure "superfluous" is drawn:
how to correct it?
P.S. alpha=0.9
What is your perspective zNear and zFar set to? Is it possible you're setting it too close and the back of your cube is being clipped? See sample below where it's set too close. That doesn't look like your issue but it's hard to tell.
Also are you sorting your polygons? When rendering transparent things you generally have to draw front to back. For a convex object like a sphere, pyramid, or cube you can draw twice with culling on, first with gl.cullFace(gl.FRONT) to draw only the backfacing triangles, the ones further from the camera, and then again with gl.cullFace(gl.BACK) to draw only the front facing triangles, the ones closer to the camera.
Yet another issue is are you correctly providing premultiplied alpha to the canvas? Most shaders do this
gl_FragColor = someUnpremultipliedAlphaColor;
But by default you need to provide pre-multiplied alpha colors
gl_FragColor = vec4(color.rgb * color.a, color.a);
Or you can set the canvas to use un-premultiplied colors
gl = someCanvas.getContext("webgl", { premultipliedAlpha: false });
window.onload = function() {
// Get A WebGL context
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
var programInfo = webglUtils.createProgramInfo(gl, ["vs", "fs"]);
var createFlattenedVertices = function(gl, vertices) {
return webglUtils.createBufferInfoFromArrays(
gl,
primitives.makeRandomVertexColors(
primitives.deindexVertices(vertices),
{
vertsPerColor: 6,
rand: function(ndx, channel) {
return channel < 3 ? ((128 + Math.random() * 128) | 0) : 255;
}
})
);
};
var bufferInfo = createFlattenedVertices(gl, primitives.createCubeVertices(1));
function degToRad(d) {
return d * Math.PI / 180;
}
var cameraAngleRadians = degToRad(0);
var fieldOfViewRadians = degToRad(60);
var uniforms = {
u_color: [1, 1, 1, 0.8],
u_matrix: null,
};
var zClose = false;
var zNear = 1;
var zFar = 3;
var zElem = document.getElementById("z");
var bElem = document.getElementById("b");
bElem.addEventListener('click', toggleZDepth, false);
toggleZDepth();
function toggleZDepth() {
zClose = !zClose;
zFar = zClose ? 3.5 : 4;
zElem.innerHTML = zFar;
}
function drawScene() {
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var aspect = canvas.clientWidth / canvas.clientHeight;
var projectionMatrix =
makePerspective(fieldOfViewRadians, aspect, zNear, zFar);
var time = Date.now() * 0.0005;
var radius = 3;
var cameraPosition = [Math.cos(time) * radius, 1, Math.sin(time) * radius];
var target = [0, 0, 0];
var up = [0, 1, 0];
var cameraMatrix = makeLookAt(cameraPosition, target, up);
var viewMatrix = makeInverse(cameraMatrix);
uniforms.u_matrix = matrixMultiply(viewMatrix, projectionMatrix);
gl.useProgram(programInfo.program);
webglUtils.setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo);
webglUtils.setUniforms(programInfo.uniformSetters, uniforms);
// draw back facing polygons first
gl.cullFace(gl.FRONT);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
// now draw front facing polygons
gl.cullFace(gl.BACK);
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
requestAnimationFrame(drawScene);
}
drawScene();
}
canvas {
border: 1px solid black;
}
#overlay {
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
}
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="//webglfundamentals.org/webgl/resources/webgl-3d-math.js"></script>
<script src="//webglfundamentals.org/webgl/resources/primitives.js"></script>
<canvas id="c" width="400" height="200"></canvas>
<div id="overlay">
<button id="b">toggle z-far</button>
<div>z-far = <span id="z"></span></div>
</div>
<!-- vertex shader -->
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 v_color;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * a_position;
v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
varying vec4 v_color;
void main() {
vec4 color = v_color * u_color;
gl_FragColor = vec4(color.rgb * color.a, color.a); // premultiply color
}
</script>
I'm working on creating an color-trail effect in webgl. Basically I'd like to be able to select a range of colors from a texture and have them trail after the texture as its animated across the screen. My strategy was to both draw the texture to the canvas as well as to a framebuffer, using the additionsl buffer to select and fade out the selected colors, resulting in a trail after the image.
I've had some success setting preserveAlphaBuffer to true and having the trail appear in the framebuffer but I can't seem to be able to get the framebuffer to blend with the backbuffer. All I get is the framebuffer with a solid background. I'm beginning to feel its not possible as I've tried just about every combination on blendFunc and blendFuncSeparate. Hopefully there's someone who can help me realize this effect?
var sketch;
function init() {
sketch = new Sketch();
document.body.appendChild(sketch);
sketch.width = window.innerWidth;
sketch.height = window.innerHeight;
loop();
}
function loop() {
requestAnimationFrame(loop);
sketch.draw();
}
var Sketch = function () {
var _canvas = document.createElement("canvas");
var _gl;
var _image;
var _program, _fboProgram;
var _loaded = false;
var _fbo, _fboTexture,_texture;
var pos = {
x: 0, y: 0
};
var speed = {
x: 10,
y: 10
}
function init() {
//start preloading image
_image = new Image();
_image.onload = function () {
setTimeout(function () {
render();
}, 1);
}
_image.src = "img/texture.png";
}
function render() {
_gl = _canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});//setupWebGL(canvas, {preserveDrawingBuffer: true});//getWebGLContext(_canvas);
initCanvas();
initFbo();
_loaded = true;
}
function initCanvas() {
_program = initShaders(_gl, "vertex-shader", "fragment-shader");
_gl.useProgram(_program);
initVertexShaderLocations(_program);
_gl.clearColor(1, 1, 1, 0); // red
_gl.clear(_gl.COLOR_BUFFER_BIT);
_texture = initTexture(_gl, _image);
_gl.enable(_gl.BLEND);
_gl.disable(_gl.DEPTH_TEST);
_gl.colorMask(1, 1, 1, 1);
}
function initFbo() {
_fboProgram = initShaders(_gl, "vertex-shader", "fbo-fragment-shader");//"2d-fragment-shader");
_gl.useProgram(_fboProgram);
initVertexShaderLocations(_fboProgram);
_texture = initTexture(_gl, _image);
// create an empty texture
_fboTexture = _gl.createTexture();
_gl.bindTexture(_gl.TEXTURE_2D, _fboTexture);
_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.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST);
_gl.texParameteri(_gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST);
_gl.texImage2D(_gl.TEXTURE_2D, 0, _gl.RGBA, _gl.canvas.width, _gl.canvas.height, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null);
// Create a framebuffer and attach the texture.
_fbo = _gl.createFramebuffer();
_gl.bindFramebuffer(_gl.FRAMEBUFFER, _fbo);
_gl.framebufferTexture2D(_gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, _fboTexture, 0);
_gl.viewport(0, 0, _gl.canvas.width, _gl.canvas.height);
_gl.clearColor(1, 1, 1, 0.2);
_gl.clear(_gl.COLOR_BUFFER_BIT);
}
function drawCanvas() {
_gl.useProgram(_program);
_gl.clearColor(1, 1, 1, 1); // red
_gl.clear(_gl.COLOR_BUFFER_BIT);
renderTexture(_program);
renderFbo(_program);
_loaded = true;
}
function drawFbo() {
_gl.bindFramebuffer(_gl.FRAMEBUFFER, _fbo);
_gl.useProgram(_fboProgram);
_gl.blendFunc(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA);
var blur = [
0, 1, 0,
1, 1, 1,
0, 1, 0
];
var kernelLocation = _gl.getUniformLocation(_fboProgram, "u_kernel[0]");
_gl.uniform1fv(kernelLocation, blur);
var textureSizeLocation = _gl.getUniformLocation(_fboProgram, "u_textureSize");
_gl.uniform2f(textureSizeLocation, _image.width, _image.height);
// Render to the texture (using clear because it's simple)
_gl.clearColor(1, 1, 1, 0); // green;
_gl.clear(_gl.COLOR_BUFFER_BIT);
renderTexture(_fboProgram);
_gl.bindFramebuffer(_gl.FRAMEBUFFER, null);
}
function renderFbo(program) {
_gl.uniform2f(program.resolutionLocation, _gl.canvas.width, _gl.canvas.height);
_gl.uniform1f(program.flipYLocation, 1);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.texCoordBuffer);
setRectangle(_gl, 0, 0, 1, 1);
_gl.enableVertexAttribArray(program.texCoordLocation);
_gl.vertexAttribPointer(program.texCoordLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.positionBuffer);
setRectangle(_gl, 0, 0, _gl.canvas.width, _gl.canvas.height);
_gl.vertexAttribPointer(program.positionLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindTexture(_gl.TEXTURE_2D, _fboTexture);
_gl.drawArrays(_gl.TRIANGLES, 0, 6);
}
function renderTexture(program) {
_gl.uniform1f(program.flipYLocation, 1.0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.texCoordBuffer);
setRectangle(_gl, 0, 0, 1, 1);
_gl.enableVertexAttribArray(program.texCoordLocation);
_gl.vertexAttribPointer(program.texCoordLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.bindBuffer(_gl.ARRAY_BUFFER, program.positionBuffer);
setRectangle(_gl, pos.x, pos.y, _image.width, _image.height);
_gl.vertexAttribPointer(program.positionLocation, 2, _gl.FLOAT, false, 0, 0);
_gl.enableVertexAttribArray(program.positionLocation);
// Tell the shader the resolution of the framebuffer.
_gl.uniform2f(program.resolutionLocation, _gl.canvas.width, _gl.canvas.height);
_gl.bindTexture(_gl.TEXTURE_2D, _texture);
_gl.drawArrays(_gl.TRIANGLES, 0, 6);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
function initVertexShaderLocations(program){
program.texCoordLocation = _gl.getAttribLocation(program, "a_texCoord");
program.positionLocation = _gl.getAttribLocation(program, "a_position");
program.resolutionLocation = _gl.getUniformLocation(program, "u_resolution");
program.flipYLocation = _gl.getUniformLocation(program, "u_flipY");
program.positionBuffer = _gl.createBuffer();
program.texCoordBuffer = _gl.createBuffer();
}
function initTexture(gl, image) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture so we can render any size image and so we are
// working with pixels.
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
return texture;
}
function initShaders(gl, vert, frag) {
// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, vert);
var fragmentShader = createShaderFromScriptElement(gl, frag);
return createProgram(gl, [vertexShader, fragmentShader]);
}
_canvas.draw = function () {
if (!_loaded)
return;
if (pos.x + 256 > _gl.canvas.width || pos.x < 0) speed.x *= -1;
if (pos.y + 256 > _gl.canvas.height || pos.y < 0) speed.y *= -1;
pos.x += speed.x;
pos.y += speed.y;
drawFbo();
drawCanvas();
}
init();
return _canvas;
}
init();
<body>
<script id="vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
uniform vec2 u_resolution;
uniform float u_flipY;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
v_texCoord = a_texCoord;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// Look up a color from the texture.
vec4 color = texture2D(u_image, v_texCoord);
if(color.a < 0.9)
color.a = 0.1;
gl_FragColor = color;
}
</script>
<script id="fbo-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_image, v_texCoord);
if(color.r > 0.5)
color.a = 0.01;
gl_FragColor = color;
}
</script>
</body>
So I did this short demo:
http://jsfiddle.net/windkiller/c46Lvbzp/
// custom fn
function createShaderFromScriptElement(gl, scriptId) {
var shaderSource = "",
shaderType;
var shaderScript = document.getElementById(scriptId);
if (!shaderScript) {
throw ("*** Error: unknown script element" + scriptId);
}
shaderSource = shaderScript.text;
if (shaderScript.type == "x-shader/x-vertex") {
shaderType = gl.VERTEX_SHADER;
} else if (shaderScript.type == "x-shader/x-fragment") {
shaderType = gl.FRAGMENT_SHADER;
} else if (shaderType != gl.VERTEX_SHADER && shaderType != gl.FRAGMENT_SHADER) {
throw ("*** Error: unknown shader type");
return null;
}
var shader = gl.createShader(shaderType);
// Load the shader source
gl.shaderSource(shader, shaderSource);
// Compile the shader
gl.compileShader(shader);
// Check the compile status
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
// Something went wrong during compilation; get the error
var lastError = gl.getShaderInfoLog(shader);
errFn("*** Error compiling shader '" + shader + "':" + lastError);
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, shaders) {
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, shaders[0]);
gl.attachShader(shaderProgram, shaders[1]);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
// HINT dont use program here
return shaderProgram;
}
I had to do few things manually, this is summary:
* createShaderFromScript and createProgram functions were missing, so I did it. Im sure you have them, so just ignore it.
* I didnt have texture, so I made base64 image with green and red colors and used it as texture. It is quite long, so I didnt append code here.
After this, demo started to work. But as you reported, it is just floating image. I tried to investigate your code, but I was too lazy and didnt have that much time.
But I had feeling that you have error in order how you work with frame buffers! So I tried to swap draw calls:
drawCanvas();
drawFbo();
And it did something! So I also:
* augment speed
* change shaders a little (just variables for alpha calculation)
* made button stop/start
Now when you run the demo and use stop, you will see two pictures, one greenred and second blended greenred, where more red = less visible (green color does bigger trail then red).
So I think you are very close to finish it. Problem must be with order how you draw in frame buffers and when you generate texture. Did you see link which I provided in comments?