Monday, July 29, 2013

Multiple render targets and deferred rendering with WebGL

Last week I added support to Spectre to render into multiple render targets using the WEBGL_draw_buffers extension. To demonstrate this new functionality, I've created the simplest possible deferred rendering setup. The example focuses just on mechanics of rendering to multiple targets and performing a fullscreen pass leaving fancy shader techniques for another post.

I render the scene into four textures, one for each of the red, green, blue, and alpha channels.

For this pass, I used the following fragment shader:

#extension GL_EXT_draw_buffers : require
precision mediump float;

/// The diffuse sampler.
uniform sampler2D diffuse;

/// The texture coodinate of the vertex.
varying vec2 texCoord;

void main()
  vec4 c = texture2D(diffuse, texCoord);
  gl_FragData[0] = vec4(c.r, 0.0, 0.0, 0.0);
  gl_FragData[1] = vec4(0.0, c.g, 0.0, 0.0);
  gl_FragData[2] = vec4(0.0, 0.0, c.b, 0.0);
  gl_FragData[3] = vec4(0.0, 0.0, 0.0, c.a);

Note that in order to access the gl_FragData variables you must include the following line in your fragment shader:

#extension GL_EXT_draw_buffers : require

The deferred rendering is done as a fullscreen post process pass. A fullscreen quad is rendered and the fragment shader reads from the red, green, blue, and alpha textures and merges them together for display.

For this pass, I used the following fragment shader:

precision mediump float;

varying vec2 samplePoint;
uniform sampler2D sourceR;
uniform sampler2D sourceG;
uniform sampler2D sourceB;
uniform sampler2D sourceA;

void main() {
  float r = texture2D(sourceR, samplePoint).r;
  float g = texture2D(sourceG, samplePoint).g;
  float b = texture2D(sourceB, samplePoint).b;
  float a = texture2D(sourceA, samplePoint).a;
  gl_FragColor = vec4(r, g, b, a);

Before rendering the scene pass you must create a render target and configure it to have four color targets. Using Spectre makes this easy:

Texture2D redColorBuffer;
Texture2D greenColorBuffer;
Texture2D blueColorBuffer;
Texture2D alphaColorBuffer;
Texture2D depthBuffer;
RenderTarget renderTarget;

int offscreenWidth = 1024;
int offscreenHeight = 1024;

// Create color buffers.
redColorBuffer = new Texture2D('redColorBuffer', graphicsDevice);
redColorBuffer.uploadPixelArray(offscreenWidth, offscreenHeight, null);
greenColorBuffer = new Texture2D('greenColorBuffer', graphicsDevice);
greenColorBuffer.uploadPixelArray(offscreenWidth, offscreenHeight, null);
blueColorBuffer = new Texture2D('blueColorBuffer', graphicsDevice);
blueColorBuffer.uploadPixelArray(offscreenWidth, offscreenHeight, null);
alphaColorBuffer = new Texture2D('alphaColorBuffer', graphicsDevice);
alphaColorBuffer.uploadPixelArray(offscreenWidth, offscreenHeight, null);
// Create depth buffer.
depthBuffer = new Texture2D('depthBuffer', graphicsDevice);
depthBuffer.pixelFormat = PixelFormat.Depth;
depthBuffer.pixelDataType = DataType.Uint32;
depthBuffer.uploadPixelArray(offscreenWidth, offscreenHeight, null);
// Create render target.
renderTarget = new RenderTarget('renderTarget', graphicsDevice);
// Use color buffers.
renderTarget.setColorTarget(0, redColorBuffer);
renderTarget.setColorTarget(1, greenColorBuffer);
renderTarget.setColorTarget(2, blueColorBuffer);
renderTarget.setColorTarget(3, alphaColorBuffer);
// Use depth buffer.
// Verify that it's renderable.
if (!renderTarget.isRenderable) {
  throw new UnsupportedError('Render target is not renderable: '

The following code configures Spectre to render into the offscreen render targets:

// Set the render target to be the offscreen buffer.

// Set the viewport (2D area of render target to render on to).
// Clear it.
graphicsContext.clearColorBuffer(0.0, 0.0, 0.0, 0.0);

After rendering the scene into the offscreen render targets, the following code configures Spectre to render into the WebGL front buffer for display:

// Use the system provided render target by switching to
// RenderTarget.systemRenderTarget.

The final step is to render a fullscreen quad with the above shader.