alpha blending in webgl works not correctly - javascript

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>

Related

WebGL Stencils, How to use a 2D sprite's transparency as a mask?

if (statuseffect) {
// Clearing the stencil buffer
gl.clearStencil(0);
gl.clear(gl.STENCIL_BUFFER_BIT);
gl.stencilFunc(gl.ALWAYS, 1, 1);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
gl.colorMask(false, false, false, false);
gl.enable(gl.STENCIL_TEST);
// Renders the mask through gl.drawArrays L111
drawImage(statuseffectmask.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
// Telling the stencil now to draw/keep only pixels that equals 1 - which we set earlier
gl.stencilFunc(gl.EQUAL, 1, 1);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// enabling back the color buffer
gl.colorMask(true, true, true, true);
drawImage(statuseffect.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
gl.disable(gl.STENCIL_TEST);
}
Im trying to get something to work like this
Where it gets the transparency of the sprite, and then draws a sprite in areas where there is no transparency, thank you.
It's not clear why you want to use the stencil for this. Normally you'd just setup blending and use the transparency to blend.
If you really wanted to use the stencil you'd need to make a shader that calls discard if the transparency (alpha) is less then some value in order to make the stencil get set only where the sprite is not transparent
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
But the thing is that would already draw the texture transparently without using the stencil.
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const texture = makeSpriteTexture(gl, '🌲', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, Math.sin(time) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: texture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Otherwise if you really want to use the stencil now that the code is discarding some pixels it should work and your code was correct. note the code below doesn't clear the stencil because it defaults to being cleared every frame
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {stencil: true});
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const starTexture = makeSpriteTexture(gl, '✱', 128);
const bugTexture = makeSpriteTexture(gl, '🐞', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, 0, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
gl.enable(gl.STENCIL_TEST);
// set the stencil to 1 everwhere we draw a pixel
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
// don't render pixels
gl.colorMask(false, false, false, false);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: starTexture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
// only draw if the stencil = 1
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// render pixels
gl.colorMask(true, true, true, true);
m4.ortho(-150, 150, 75, -75, -1, 1, mat);
m4.translate(mat, [0, Math.cos(time * 1.1) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: bugTexture,
u_alphaTest: 0, // draw all pixels (but stencil will prevent some)
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Let me also point out that that this is also probably better done using alpha blending, passing both textures into a single shader and passing in another matrix or other uniforms to apply one texture's alpha ot the other. This would be more flexible as you could can blend across all values of 0 to 1 where as with the stencil you can only mask 0 or 1 period.
My point isn't to say "don't use the stencil" but rather that there are times where it's best and times where it's not. Only you can know for your situation which solution to choose.

Why am i getting "out is null" (glmatrix) error? Webgl

The goal is a textured open cylinder. I believe the issue is with the setCamera() function. I havent made any changes from when I used the function as it is previously. Running on an http-server localhost in firefox(javascript is enabled). I would really appreciate a solution on how to make this work as well as any tips.
"glmatrix" is a Javascript Matrix and Vector library.
Here is the code:
var mat4 = glMatrix.mat4;
var segments = 64;
var cylVertices=[]; //sidevertices
var positionAttribLocation = null;
var gl = null;
var program = null;
var cylVertexBufferObject = null;
var matWorldUniformLocation =null;
var matViewUniformLocation =null;
var matProjectionUniformLocation =null;
var worldMatrix = null;
var viewMatrix = null;
var projectionMatrix = null;
var canvas = null;
var vertexShaderText =
[
'precision mediump float;',
'',
'attribute vec3 vertPosition;',
'attribute vec2 vertTexCoord;',
'varying vec2 fragTexCoord;',
'uniform mat4 mWorld;',
'uniform mat4 mView;',
'uniform mat4 mProjection;',
'',
'void main()',
'{',
' fragTexCoord = vertTexCoord;',
' gl_Position = mProjection * mView * mWorld * vec4(vertPosition, 1.0);',
'}'
].join('\n');
var fragmentTexShaderText =
[
'precision mediump float;',
'',
'varying vec2 fragTexCoord;',
'uniform sampler2D sampler;',
'void main()',
'{',
' gl_FragColor = texture2D(sampler,fragTexCoord);',
'}'
].join('\n');
var initDemo = function () {
console.log('This is working');
canvas = document.getElementById('game-surface');
gl = canvas.getContext('webgl');
if (!gl) {
console.log('WebGL not supported, falling back on experimental-webgl');
gl = canvas.getContext('experimental-webgl');
}
if (!gl) {
alert('Your browser does not support WebGL');
}
clear();
gl.enable(gl.DEPTH_TEST);//enable drawing over if closer to the virtual camera
//
//Create Shaders
//
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader,vertexShaderText);
gl.shaderSource(fragmentShader,fragmentTexShaderText);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader,gl.COMPILE_STATUS)){
console.error('ERROR compiling vertex shader!', gl.getShaderInfoLog(vertexShader));
return;
}
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)){
console.error('ERROR compiling fragment shader!', gl.getShaderInfoLog(fragmentShader));
return;
}
program = gl.createProgram();
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader)
gl.linkProgram(program);
if (!gl.getProgramParameter(program,gl.LINK_STATUS)){
console.error('ERROR linking program!', gl.getProgramInfoLog(program));
return;
}
a=0, b=0, y=0, u=0, v=0, s=0, t=0; //The origin
r=1.0,g=0.4,b=0.6;
theta = (Math.PI/180) * (360);
//theta = (Math.PI/180) * (360/(segments)); //Degrees = radians * (180 / )
for (i =0;i<=(segments);i++){
x = i*Math.cos(theta);
z = i*Math.sin(theta);
s=(theta+180)/360;
cylVertices.push(x, y, z, s,0); //Sidevertices along the bottom
cylVertices.push(x, y+2, z, s,2); //Sidevertices along the top
}
cylArray = new Float32Array(cylVertices); //sidearray
//
//------MAIN RENDER LOOP-------
//
var angle = 0;
var loop = function (){
setCamera();
angle = performance.now() / 1000 / 6 * 2 * Math.PI;
mat4.rotate(xRotationMatrix,identityMatrix, angle, [0,1,0]);//x rotation
gl.uniformMatrix4fv(matWorldUniformLocation, gl.FALSE, xRotationMatrix);
clear();
theVertexBufferObject = gl.createBuffer();
drawCylSide();
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
};
//
// --------------------functions--------------------
//
function drawCylSide(){
gl.bindBuffer(gl.ARRAY_BUFFER,theVertexBufferObject);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cylArray),gl.STATIC_DRAW);
setPointer(5);
var texCoordAttribLocation = gl.getAttribLocation(program,'vertTexCoord');
gl.vertexAttribPointer(
texCoordAttribLocation, //Attribute location
2, //Number of vertix elements
gl.FLOAT, //Type of elements
gl.FALSE, //Normalised
5 * Float32Array.BYTES_PER_ELEMENT, //Size of individual vertex
3 * Float32Array.BYTES_PER_ELEMENT, //Offset
);
gl.enableVertexAttribArray(positionAttribLocation);
gl.enableVertexAttribArray(texCoordAttribLocation);
var boxTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, boxTexture);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,gl.LINEAR);
gl.texParameter(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER,gl.LINEAR);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
gl.UNSIGNED_BYTE,
document.getElementById('wall-img')
);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.useProgram(program);
gl.bindTexture(gl.TEXTURE_2D, boxTexture);
gl.activeTexture(gl.TEXTURE0);
gl.drawArrays(gl.TRIANGLE_STRIP,0, segments);
};
function setPointer(n){
positionAttribLocation = gl.getAttribLocation(program,'vertPosition');
gl.vertexAttribPointer(
positionAttribLocation, //Attribute location
3, //Number of vertix elements
gl.FLOAT, //Type of elements
gl.FALSE, //Normalised
n * Float32Array.BYTES_PER_ELEMENT, //Size of individual vertex
0 //Offset
);
};
function clear(){
gl.clearColor(0.75, 0.85, 0.8, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
};
function setCamera(){
mat4.identity(worldMatrix);
mat4.lookAt(viewMatrix,[0,1,-8],[0,0,0],[0,10,0]);
mat4.perspective(projectionMatrix,glMatrix.glMatrix.toRadian(45),canvas.width/canvas.height,0.1,1000.0);
gl.uniformMatrix4fv(matWorldUniformLocation,gl.FALSE,worldMatrix);
gl.uniformMatrix4fv(matViewUniformLocation,gl.FALSE,viewMatrix);
gl.uniformMatrix4fv(matProjectionUniformLocation,gl.FALSE,projectionMatrix);
};
You need to initialize vectors and matrices using the respective create function of glMatrix.
var worldMatrix = mat4.create();
var viewMatrix = mat4.create();
var projectionMatrix = mat4.create();

How to draw lines on top of triangle?

Is there a way to draw a gl.LINE_LOOP always on top of gl.TRIANGLES while still using same program and points.
Yes but gl.LINE_LOOP will always connect the first and last points which is probably not what you want.
In any case the default depth function is gl.LESS meaning with depth testing on WebGL will only render pixels if their depth value is less than that pixel's current depth value. So, change it to gl.LEQUAL and you should get what you want.
gl.depthFunc(gl.LEQUAL);
"use strict";
twgl.setDefaults({attribPrefix: "a_"});
const v3 = twgl.v3;
const m4 = twgl.m4;
const gl = twgl.getWebGLContext(document.getElementById("c"));
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 16, 8);
var uniforms = {
u_lightDir: twgl.v3.normalize([1, 20, -10]),
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
var projection = m4.perspective(fov, aspect, zNear, zFar);
var eye = [1, 3, -5];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(projection, view);
var world = m4.rotationY(time);
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniform
twgl.setUniforms(programInfo, uniforms);
twgl.setUniforms(programInfo, {
u_ambient: [0, 0, 0],
u_diffuse: [1, 0, 0],
});
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
// calls gl.uniform
twgl.setUniforms(programInfo, {
u_ambient: [1, 1, 0],
u_diffuse: [0, 0, 0],
});
gl.drawElements(gl.LINE_LOOP, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
<canvas id="c"></canvas>
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform mat4 u_world;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
varying vec3 v_normal;
void main() {
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
gl_Position = u_worldViewProjection * a_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec3 v_normal;
uniform vec3 u_lightDir;
uniform vec3 u_diffuse;
uniform vec3 u_ambient;
void main() {
vec3 a_normal = normalize(v_normal);
float light = dot(a_normal, u_lightDir) * .5 + .5;
gl_FragColor = vec4(u_diffuse * light + u_ambient, 1);
}
</script>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

WebGL Basics: Using Render Loops to Apply 2D Convolution Filters to Buffer Canvases

Very new to WebGL and attempting to port some 2D image processing shaders in order to get a handle on things. I initially was misled by the MDN tutorials into thinking WebGL was like OpenGL desktop, but then found these tutorials, which I found much more true to form as well as my purposes. However, I'm still having some trouble in formatting a render loop so I can pass a continually updating texture for processing. In cases where it does render, I just get a muddy mess and in cases where the vertex shader isn't a simple pass through I get nothing. I understand GLSL and the basics of how buffers work, but clearly am doing something very wrong here... Any help would be greatly appreciated. Thanks!
class GL {
constructor(canvas){
this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!this.gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
this.gl = null;
}
//init shaders
var fragmentShader = getShader(this.gl, "fshader");
var vertexShader = getShader(this.gl, "vshader");
var shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader);
this.gl.attachShader(shaderProgram, fragmentShader);
this.gl.linkProgram(shaderProgram);
this.gl.useProgram(shaderProgram);
this.positionLocation = this.gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = this.gl.getAttribLocation(shaderProgram, "texcoord");
var resolutionLocation = this.gl.getUniformLocation(shaderProgram, "resolution");
this.width = this.gl.getUniformLocation(shaderProgram, "width");
//set resolution
this.gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
theSource = "";
currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == currentChild.TEXT_NODE) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y) {
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
//texture coordinates
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
this.gl.enableVertexAttribArray(this.texCoordLocation);
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.bufferData(
this.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]),
this.gl.STATIC_DRAW);
//create texture
var texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
//normalize image to powers of two
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
//load texture from 2d canvas
this.gl.texImage2D(this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
bufferCanvas);
//load buffer
var buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.enableVertexAttribArray(this.positionLocation);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
//draw size and position
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), this.gl.STATIC_DRAW);
//blur width
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
//draw
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
canvasGL.width = 5.0;
for(var i=0; i<10; i++) {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"), canvas2d.getBoundingClientRect("top"));
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sylvester/0.1.3/sylvester.min.js"></script>
<script src="https://github.com/mdn/webgl-examples/blob/gh-pages/tutorial/glUtils.js"></script>
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform float width;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + vec2(-width, -width);
texcoord02 = texcoord + vec2( width, -width);
texcoord20 = texcoord + vec2( width, width);
texcoord22 = texcoord + vec2(-width, width);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
gl_FragColor = 0.2 * blur;
}
</script>
</head>
<body>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="visibility:hidden;"></canvas>
</body>
There's several issues with the code
neither of the script tags seem to be needed
You assign this.width to a uniform location
this.width = this.gl.getUniformLocation(shaderProgram, "width");
But then later destroy that with
canvasGL.width = 5.0
width is a uniform but you're trying to set it as an attribute
wrong
this.gl.enableVertexAttribArray(this.width);
this.gl.vertexAttribPointer(this.width, 1, this.gl.FLOAT, false, 12, 8);
right
this.gl.uniform1f(this.width, whateverYouWantedWidthToBe);
You've got unneeded strides on all your attributes
wrong
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 8, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 12, 0);
right
this.gl.vertexAttribPointer(this.texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
this.gl.vertexAttribPointer(this.positionLocation, 2, this.gl.FLOAT, false, 0, 0);
Well I suppose the texcoord one is not wrong if you want to set strides but the position one is wrong since you're using 2 floats per position not 3. Why not just set them to 0?
You're assigning the texCoordLocation to a var but then using it as a property on this
wrong
var texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
right?
this.texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
On top of that the structure of the code is probably not what you intended.
In WebGL you don't generally call gl.createXXX functions in your render function. You'd call gl.createXXX functions those during initialization. See here for a more typical structure.
It's not clear at all what this line is trying to do
canvasGL.render(canvas2d, canvas2d.getBoundingClientRect("left"),
canvas2d.getBoundingClientRect("top"))
What do you think canvas2d.getBoundingClientRect() is going to return that's useful for rendering? Also that's not how getBoundingClientRect works. It returns a rect, it doesn't take any arguments.
Once you have all that fixed it's not clear what you want width to be. I'm assuming you want it to be pixels but in that case it needs to be 1 / canvas2D.width and you need a separate value for height in your shader since you'll need different values to move up and down a certain number of pixels vs left and right.
other suggestions
Pull gl into a local variable. Then you don't have to do this.gl everywhere. Less typing, shorter, and faster
You can get the contents of script tag with just this
var theSource = shaderScript.text;
You're checking for shader compile errors but not link errors.
visibility: hidden; doesn't do what I think you think it does. You probably want display: none;.
Here's one version that does something.
class GL {
constructor(canvas){
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
return;
}
this.gl = gl;
//init shaders
var fragmentShader = getShader(gl, "fshader");
var vertexShader = getShader(gl, "vshader");
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("An error occurred linking the shaders: " + gl.getProgramInfoLog(shaderProgram));
return;
}
this.shaderProgram = shaderProgram;
this.positionLocation = gl.getAttribLocation(shaderProgram, "position");
this.texCoordLocation = gl.getAttribLocation(shaderProgram, "texcoord");
this.resolutionLocation = gl.getUniformLocation(shaderProgram, "resolution");
this.blurOffsetLocation = gl.getUniformLocation(shaderProgram, "blurOffset");
// init texture coordinates
this.texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
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);
// create position buffer
this.positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
//create texture
this.texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this.texture);
//normalize image to powers of two
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);
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var theSource = shaderScript.text;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
};
render(bufferCanvas, x, y, blurAmount) {
var gl = this.gl;
gl.clear(this.gl.COLOR_BUFFER_BIT);
gl.useProgram(this.shaderProgram);
// setup buffers and attributes
gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer);
gl.enableVertexAttribArray(this.texCoordLocation);
gl.vertexAttribPointer(this.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
//load buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
gl.enableVertexAttribArray(this.positionLocation);
gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0);
//draw size and position
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + bufferCanvas.width, y,
x, y + bufferCanvas.height,
x, y + bufferCanvas.height,
x+ bufferCanvas.width, y,
x+ bufferCanvas.width, y + bufferCanvas.height]), gl.STATIC_DRAW);
//load texture from 2d canvas
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
gl.RGBA,
gl.UNSIGNED_BYTE,
bufferCanvas);
//blur width
gl.uniform2f(this.blurOffsetLocation,
blurAmount / bufferCanvas.width,
blurAmount / bufferCanvas.height);
//set resolution
gl.uniform2f(this.resolutionLocation,
gl.canvas.width,
gl.canvas.height);
//draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
};
var canvas2d = document.getElementById('buffer-canvas');
var context2d = canvas2d.getContext("2d");
var canvasGL = new GL(document.getElementById('main-canvas'));
function render(time) {
time *= 0.001;
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var a = Math.floor(Math.random() * 255);
context2d.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a + ")";
var x = Math.random() * canvas2d.width;
var y = Math.random() * canvas2d.height;
var width = canvas2d.width - (Math.random() * canvas2d.width);
var height = canvas2d.height - (Math.random() * canvas2d.height);
context2d.fillRect(x, y, width , height);
canvasGL.render(canvas2d, 0, 0, Math.sin(time) * 5);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script id="vshader" type="x-shader/x-vertex">
precision mediump float;
attribute vec2 position;
attribute vec2 texcoord;
uniform vec2 resolution;
uniform vec2 blurOffset;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
gl_Position = vec4(((position / resolution) * 2.0 - 1.0) * vec2(1, -1), 0, 1);
// get texcoords
texcoord11 = texcoord;
texcoord00 = texcoord + blurOffset * vec2(-1, -1);
texcoord02 = texcoord + blurOffset * vec2( 1, -1);
texcoord20 = texcoord + blurOffset * vec2( 1, 1);
texcoord22 = texcoord + blurOffset * vec2(-1, 1);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D image;
varying vec2 texcoord11;
varying vec2 texcoord00;
varying vec2 texcoord02;
varying vec2 texcoord20;
varying vec2 texcoord22;
void main()
{
vec4 blur;
blur = texture2D(image, texcoord11);
blur += texture2D(image, texcoord00);
blur += texture2D(image, texcoord02);
blur += texture2D(image, texcoord20);
blur += texture2D(image, texcoord22);
// do you really want to blend the alpha?
gl_FragColor = 0.2 * blur;
}
</script>
<canvas id="main-canvas" width="400" height="300" style="border:1px solid black;"></canvas>
<canvas id="buffer-canvas" width="400" height="300" style="display: none;"></canvas>

Transparent frame buffers in webgl

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?

Categories

Resources