WebGL Post Processing
Details: Short Tutorial
Overview
This article discusses a few details regarding post processing with WebGL. Most WebGL post processing examples, at code.Thunders.biz render to a frame buffer or depth buffer, switch shader programs, process the rendered texture with new shaders, then display the result to the canvas. The first program prepares the basic rendered frame. The second program prepares post processing.
This tutorial provides information regarding rendering to a framebuffer and rendering to a depth texture. The end of the article includes the entire blur post processing vertex and fragment shaders used in the Cube Blurs with Post Processing example. The blur post processing shaders execute after the basic rendering shaders.
Frame Buffers
Create and bind a frame buffer with
the following two WebGL methods.
Method createFramebuffer()
initializes a frame buffer.
Method bindFramebuffer()
binds the framebuffer to a target.
Constant gl.FRAMEBUFFER
collects or stores data for color, alpha, depth and stencil
buffers.
Variable fb
is a frame buffer.
Variable gl
is a WebGLRenderingContext
.
var fb = gl.createFramebuffer(); gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
Employ two programs for post processing. The first program includes shaders which render the scene. The second program includes shaders which process the scene.
During rendering
first activate the program
which renders the scene with basic rendering shaders.
Variable programRender
is
a WebGLProgram
with valid vertex and fragment shaders.
gl.useProgram( programRender );
Second bind the frame buffer. Then render with the basic rendering shaders. The scene renders to the framebuffer rather than the canvas.
gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
Third activate the program
used for post processing.
Variable programPostProcess
is
a WebGLProgram
with valid vertex and fragment shaders.
The Blur Shaders
section demonstrates vertex and fragment shaders
assigned to programPostProcess
.
The Blur Shaders
perform post processing.
gl.useProgram( programPostProcess );
Fourth assign the framebuffer's texture unit to
the sampler accessed within the post process shaders.
Variable TEX_COLOR
in the following listing
is the number of the texture unit assigned to the framebuffer.
Variable uSampler0
is the location
of the Sampler2D
within the post processing shader.
Sampler2D uSampler0
samples
values from the framebuffer.
The goal is to modify the pre-rendered scene with the new shaders.
gl.uniform1i ( uSampler0, TEX_COLOR );
Last render a square with the post processing shaders. The square displays on the canvas. The first program renders, any range of mesh elements, to the frame buffer. The second program renders data from the framebuffer to a square quad for display on the canvas.
The Cube Blurs with Post Processing example, uses a set of vertices which render a simple quad. A quad's just a rectangular mesh. You can substitute the cube's blurring shaders for other post processing shaders. The secondary set of shaders operate on texels which render to the quad.
Depth Textures
This section provides a few tips regarding how to use WebGL depth textures.
First determine if
depth textures are available within the current browser.
Variable gl
is a WebGLRenderingContext
.
Method getExtension()
returns a
boolean
value.
Parameter WEBGL_depth_texture
requests
depth texture availability.
In the following listing, if depth texturing
is available then bDepthTextureExtension
equals true
.
this.bDepthTextureExtension = gl.getExtension( "WEBGL_depth_texture" );
Next enable depth testing with the WebGL API method enable()
.
Call gl.enable(gl.DEPTH_TEST)
on the WebGLContext
.
Check WebGL context attributes to verify
depth testing has been enabled.
The following WebGLContext
attributes include
depth: true
. If depth: true
then depth testing is both available and enabled.
WebGLContext attributes: alpha:true antialias:true depth:true stencil:false premultipliedAlpha:true
Depth Texture Tips
Currently Windows Phone 8.1 default Web browser doesn't support depth textures. The default color shader displays. Sometimes it's helpful to let the user know depth testing isn't available. Other times it's helpful to include fall back rendering.
Blur Shaders
This section includes vertex and fragment shaders used to blur a scene.
Vertex Shader
The following vertex shader first multiplies the
perspective matrix, transformation matrix,
and current vertex attribute.
The product is assigned to built in
vector gl_Position
.
Last the vertex shader assigns
the current texel attribute to
the varying v_tex_coord0
for processing in the GPU and fragment shader.
More Efficent Options
Post processing often simply renders to a square plane or quad. Possibly leave out the perspective projection matrix. Perhaps use a constant transformation matrix.
attribute vec4 a_position; attribute vec2 a_tex_coord0; varying vec2 v_tex_coord0; uniform mat4 um4_matrix; uniform mat4 um4_pmatrix; void main(void) { gl_Position = um4_pmatrix * um4_matrix * a_position; v_tex_coord0 = a_tex_coord0;
Fragment Shader
One method to blur an image involves mixing color samples taken from the same image, at different offsets.
The following fragment shader uses a uniform
named u_post
to determine the amount of
blur to provide for the particular rendering frame.
Each frame uploads a new value to u_post
in
JavaScript.
More Efficent Options
It would be more efficient to
divide u_post
by 100
in JavaScript. The fragment shader runs multiple
times per frame, but JavaScript only runs once per frame.
For efficiency first process as much as possible in JavaScript. Second process as much as possible in the vertex shader. Third process what's needed in the fragment shader. Fragment shaders run more often than vertex shaders. Vertex shaders run more often than JavaScript animation frames, unless there's just one vertex to render. This particular example could improve but it does work.
Notice each call to texture2D()
uses
a different texel offset to sample color from
the same texture.
In this case, the texture comes from the frame buffer.
precision mediump float; uniform sampler2D u_sampler0; varying vec2 v_tex_coord0; uniform float u_post; void main(void) { float blur = u_post/100.0; vec4 color0 = texture2D( u_sampler0, vec2(v_tex_coord0.s - blur, v_tex_coord0.t - blur) ); color0.a = 0.75; vec4 color1 = texture2D( u_sampler0, vec2(v_tex_coord0.s + blur, v_tex_coord0.t + blur) ); color1.a = 0.75; vec4 color2 = texture2D(u_sampler0, vec2(v_tex_coord0.s + blur, v_tex_coord0.t - blur) ); color2.a = 0.75; vec4 color3 = texture2D(u_sampler0, vec2(v_tex_coord0.s - blur, v_tex_coord0.t + blur) ); color3.a = 0.75; vec4 color4 = texture2D( u_sampler0, v_tex_coord0 ); gl_FragColor = ( color0 + color1 + color2 + color3 + color4)/5.0; }
Summary
This article discussed a few details regarding post processing with WebGL. Most examples render to a frame or depth buffer, switch shader programs, process the rendered texture with new shaders, then display the result to the canvas. The first program prepares the basic rendered frame. The second program prepares post processing.
This tutorial provided information regarding rendering to a framebuffer and rendering to a depth texture. The end of the article includes the entire blur post processing vertex and fragment shaders used in the Cube Blurs with Post Processing example. The blur post processing shaders execute after the basic rendering shaders.