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>