Skip navigation

I posted the following on the Card Kingdom development blog.

One neat concept I learned about while researching deferred rendering was the PBuffer. The PBuffer is used for post-process effects and contains at least two screen-sized render targets that are swapped between each other while rendering each post-process effect sequentially. The final image from one effect is used as input for the next, creating a daisy chain of accumulating effects. To expand the number of possible effects the PBuffer can support, half and quarter-sized buffers can be used to scale down the previously rendered effect and used to support other effects (e.g. bloom).

For Card Kingdom, the following PBuffer serialization is used:

<ZeroPoint::Rendering::PBuffer>
  <Effects>
    <Effect>
      <ZeroPoint::Rendering::PBufferEffect
        index="0"
        render-half-quarter="false"
        shader="pbufferLightingEffect" />
    </Effect>
    <Effect>
      <ZeroPoint::Rendering::PBufferEffect
        index="10"
        render-half-quarter="false"
        shader="pbufferEdgeEffect" />
    </Effect>
    <Effect>
      <ZeroPoint::Rendering::PBufferEffect
        index="20"
        render-half-quarter="false"
        shader="pbufferDepthOfFieldEffect" />
    </Effect>
    <Effect>
      <ZeroPoint::Rendering::PBufferEffect
        index="100"
        render-half-quarter="false"
        shader="pbufferFXAAEffect" />
    </Effect>
  </Effects>
</ZeroPoint::Rendering::PBuffer>

For this implementation, index is used to order the effects easily, render-half-quarter is used to tell the PBuffer whether or not to render to the half and quarter-sized buffers, and shader defines what post-process effect to use. With this setup, it is quite easy to add, remove, replace, and reorder post-process effects without having to recompile the entire project.

The first effect, pbufferLightingEffect, combines the diffuse, specular, and lighting buffers together to give a base for all the effects, so it has be the first one. The next effect, pbufferEdgeEffect, uses the information from the edge detection pass to render an outline where an edge was found. Next, pbufferDepthOfFieldEffect uses the depth buffer and the previous rendered image to blur pixels that are out of focus (i.e. too far or too close to the camera). The last effect, pbufferFXAAEffect, is a full-screen anti-aliasing pass that uses the fast approximate anti-aliasing method. Because it’s an anti-aliasing pass, it has to be the last effect.

Here are each stage of the post-process effects separated:

Lighting only.

Lighting and Edges.

Lighting, Edges, and Depth of Field.

Lighting, Edges, Depth of Field, and FXAA.

As you can see, each effect accumulates to the final image. FXAA softens the edges from the depth of field, which blurs the edges from the edge detection pass and the lighting, diffuse, and specular combination. If the edge effect was swapped with the depth of field effect, edges would be rendered on top of the blurred image, giving all edges a sharper look no matter how far from the camera they were. This effect order renders as such:

Lighting, Depth of Field, Edge, and FXAA.

Although this looks nice, it is not as desirable as the original order because the edges on out of focus objects are the same as in focus objects, losing a lot of the depth of field effect. This is noticeable when comparing the last two example images.