glslwebglfragment-shader

How to implement Shadertoy buffers in WebGL Javascript when trying to convert to JS?


I'm attempting to convert a Shadertoy to Javascript and WebGL so that it can run independently from Shadertoy. Shadertoy has the concept of a buffer, in this example it re-cycles the buffer and improves the output image. It does this on the Buf A tab.

https://www.shadertoy.com/view/MdyGDW

It does this by writing its output to buffer A which is bound to iChannel0 and then it reads from the same iChannel0 on each draw cycle. How can I implement this concept of buffers in WebGL Javascript fragment shaders, WebGL uses the GLSL language. Specifically in this case it is able to write to the buffer and then read from the same buffer on the next render cycle.


Solution

  • Shadertoy uses technique called rendering to texture. Suppose gl is our WebGL context. First we need to create a texture first pass will draw to:

    // desired size of the texture
    const W = 800, H = 600;
    const textureA = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, textureA);
    // allocate texture data.
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // may be change texture parameters (at least magnification and
    // minification filters since we won't render mip levels.
    

    Then we create framebuffer object so we can draw to our texture:

    const framebufferA = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferA);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureA, 0);
    

    Now we can draw:

    gl.bindBuffer(gl.FRAMEBUFFER, framebufferA);
    gl.viewport(0, 0, W, H);
    
    // draw first pass, the one which supposed to write data for the channel 0
    // it'll use fragment shader for bufferA
    
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    
    // pass textureA as channel 0
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, textureA);
    gl.uniform1i(channel0Uniform, 0);
    
    // draw second pass, the one with uses channel 0
    

    There're a lot of materials about rendering to texture, for example here or here.