Rendered Texture

By default, WebGL draws on the default color framebuffer, causing the canvas to display the drawing. You may draw on a manually created framebuffer in the memory, without showing the drawing on the canvas. The drawing may then be used as a texture.

To initialize a framebuffer object:

Step 1: Create a framebuffer object. This is done by calling createFramebuffer(). The framebuffer object may be deleted by calling deleteFramebuffer(framebuffer).

Step 2: Prepare a texture object.

Step 3: Prepare a renderbuffer object for the depth buffer.
A renderbuffer object may be created by createRenderbuffer(). (It can be deleted by deleteRenderbuffer().) Then, bind it by calling bindRenderbuffer(gl.RENDERBUFFER, renderbuffer). When the binding is complete, specify the storage parameters by calling renderbufferStorage(gl.RENDERBUFFER, internalformat, width, height).

internalformat
DEPTH_COMPONENT16 This uses the renderbuffer as a depth buffer.
STENCIL_INDEX8 This uses the renderbuffer as a stencil buffer.
RGBA4 This uses the renderbuffer as a color buffer. Each component has 4 bits.
RGB5_A1 This uses the renderbuffer as a color buffer. Each RGB component has 5 bits, and A has 1 bit.
RGB565 This uses the renderbuffer as a color buffer. Each RGB component has 5, 6, 5 bits respectively,

Step 4: Attach the texture object to the framebuffer obect,
First, bind the framebuffer by calling bindFramebuffer(gl.FRAMEBUFFER, framebuffer). Then, set the texture object by calling framebufferTexture2D(gl.FRAMEBUFFER, attachment, textarget, texture, level), where 'attachment' can be 'COLOR_ATTACHMENT0' for a color buffer, or 'DEPTH_ATTACHMENT' for a depth buffer.

Step 5: Attach the renderbuffer object to the framebuffer object.
This is done by calling framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer), where 'attachment' can be 'COLOR_ATTACHMENT0', 'DEPTH_ATTACHMENT' or 'STENCIL_ATTACHMENT'.

Step 6: Check the configuration of the framebuffer object.
This is done by calling checkFramebufferStatus(gl.FRAMEBUFFER).

Return value for checkFramebufferStatus()
FRAMEBUFFER_COMPLETE: Correct configuration.
FRAMEBUFFER_INCOMPLETE_ATTACHMENT: An incomplete framebuffer attachment point.
FRAMEBUFFER_INCOMPLETE_DEMENSIONS: The width or height of the attachment is different.
FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: No valid attachment found.

To draw in the framebuffer object, call:
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

To draw in the default color framebuffer, call:
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

This example renders a textured image in the framebuffer, and then uses the framebuffer as a texture for a square. Although this is not particularly interesting, it clearly illustrates the technique by which rich effects can be accomplished. For example, with this, you can animate a square containing a rotating cube with a texture image on each side.
RESETRUNFULL
// /shared/webgl-library.js
function initFramebufferObject(gl, width, height) {
   var framebuffer, texture, depthBuffer;
   framebuffer = gl.createFramebuffer();

   texture = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
   gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
   framebuffer.texture = texture;
   depthBuffer = gl.createRenderbuffer();
   gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
   gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
   gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
   gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
   gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
   
   var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
   if (gl.FRAMEBUFFER_COMPLETE !== e) {
      alert('Frame buffer object is incomplete: ' + e.toString());
      return error();
   }   // Unbind the buffer object
   gl.bindFramebuffer(gl.FRAMEBUFFER, null);
   gl.bindTexture(gl.TEXTURE_2D, null);
   gl.bindRenderbuffer(gl.RENDERBUFFER, null);
   return framebuffer;
}

<!DOCTYPE html><html><head>
<script src="/shared/webgl-library.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
   attribute vec4 a_Position;
   attribute vec2 a_TexCoord;
   varying vec2 v_TexCoord;
   void main() {
      gl_Position = a_Position;
      v_TexCoord = a_TexCoord;
   }
</script>
<script id="shader-fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision mediump float;
   #endif
   uniform sampler2D u_Sampler;
   varying vec2 v_TexCoord;
   void main() {
      gl_FragColor = texture2D(u_Sampler, v_TexCoord);
   }
</script>
<script>
function WebGLStart() {
   var canvas = document.getElementById('cv');
   var gl = canvas.getContext('webgl');
   initShaders(gl, "shader-vs", "shader-fs");
   img = new Image();
   img.onload = function(){
      var fbo = draw_in_framebufferObject(gl, img);
      draw_in_defaultFramebuffer(gl, fbo.texture);
   }
   img.src = '/shared/webgl_dog.jpeg';
}
function draw_in_framebufferObject(gl, img){
   var n=initVertexBuffers(gl);
   var texture = gl.createTexture();
   var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
   
   var fbo = initFramebufferObject(gl, 512, 512);
   gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
   gl.viewport(0, 0, 512, 512);
   gl.clearColor(0.7, 0.7, 0.0, 1.0);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   
   loadTexture(gl, n, texture, u_Sampler, img, gl.TEXTURE0, 0);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
   return fbo;
}
function draw_in_defaultFramebuffer(gl, texture){
   var n=initVertexBuffers(gl);
   
   gl.bindFramebuffer(gl.FRAMEBUFFER, null);
   gl.viewport(0, 0, 500, 500);
   gl.clearColor(0.0, 0.0, 0.0, 1.0);
   gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   
   gl.activeTexture(gl.TEXTURE0);
   gl.bindTexture(gl.TEXTURE_2D, texture);
   gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}
function initVertexBuffers(gl) {
   var verticesTexCoords = new Float32Array([     // Vertex coordinate --> Texture coordinate
     -0.8,  0.8,   0.0, 1.0,
     -0.8, -0.8,   0.0, 0.0,
      0.8,  0.8,   1.0, 1.0,
      0.8, -0.8,   1.0, 0.0,
   ]);
   var n = 4; // The number of vertices
   
   var vertexTexCoordBuffer = gl.createBuffer();
   
   gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
   gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
   
   var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
   
   var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
   gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE*4, 0);
   gl.enableVertexAttribArray(a_Position);
   
   var a_TexCoord=gl.getAttribLocation(gl.program, 'a_TexCoord');
   gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
   gl.enableVertexAttribArray(a_TexCoord);
   
   return n;
}
</script>
</head>
<body onload="WebGLStart();">
   <canvas id="cv" style="border: none;" width="500" height="500"></canvas>
</body></html>

isFramebuffer(fb) returns true if 'fb' is a framebuffer object.

framebufferTexture2D(gl.FRAMEBUFFER, attachment, textarget, texture, level) attaches a texture image to a framebuffer object.
'attachment' can be 'COLOR_ATTACHMENT0', 'DEPTH_ATTACHMENT' or 'STENCIL_ATTACHMENT'.
'textarget' can be 'TEXTURE_2D', 'TEXTURE_CUBE_MAP_POSITIVE{X,Y,Z}' or'TEXTURE_CUBE_MAP_NEGATIVE{X,Y,Z}'.

getFramebufferAttachmentParameter(gl.FRAMEBUFFER, attachment, pname) returns attachment parameters for a framebuffer object.
'attachment' can be 'COLOR_ATTACHMENT0', 'DEPTH_ATTACHMENT' or 'STENCIL_ATTACHMENT'.
'pname' can be
        FRAMEBUFFER_ATTACHMENT_OBJECT_{TYPE,NAME},
        FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, or
        FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE.

isRenderbuffer(rbf) returns true if 'rbf' is is a renderbuffer object.

getRenderbufferParameter(gl.RENDERBUFFER, pname) returns the paratemeters for a renderbuffer. 'pname' can be
        RENDERBUFFER_{WIDTH, HEIGHT, INTERNAL_FORMAT},
        RENDERBUFFER_{RED, GREEN, BLUE, ALPHA, DEPTH, STENCIL}_SIZE.