2

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>
Kev
  • 59
  • 4
  • Welcome to stackoverflow! You do have a lot of redundant calls in your code so you may want to go back a few steps and work your way through every step and make sure you know what every call does, asserting your assumptions of the current state with methods of the "getParameter"-family ("getShaderParameter", "getProgramParameter",...) look them up here https://www.khronos.org/registry/webgl/specs/latest/1.0/ – Winchestro Jan 27 '15 at 13:42
  • Could you please include your shaders or better, publish it with http://jsfiddle.net/ for example? Arnt you looking for this: http://youtu.be/rfQ8rKGTVlg?t=31m46s ? – Entity Black Jan 27 '15 at 14:05
  • 2
    Okay, I've taken out the redundancies, and included the shaders I'm using. I wasn't able to get a jsfiddle example working, because of issues with the image I was using, but hopefully it's clear what I'm trying to do. You'll see the fbo shader is removing all red from the image, as I'm attempting to only trail the other colors (the texture I'm using is only red and green, so I'd like to see all the green colors trailing after the shape) – Kev Jan 28 '15 at 10:24

1 Answers1

0

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?

Entity Black
  • 3,401
  • 2
  • 23
  • 38