2. JitteringJittering is somewhat more complex to
implement than supersampling, but it’s not rocket science. The idea is
to rerender the scene multiple times at slightly different viewpoints,
merging the results along the way. You need only two FBOs for this
method: the on-screen FBO that accumulates the color and the offscreen
FBO that the 3D scene is rendered to. You can create as many jittered
samples as you’d like, and you still need only two FBOs. Of course, the
more jittered samples you create, the longer it takes to create the
final rendering. Example 5 shows the
pseudocode for the jittering algorithm. Example 5. Jitter pseudocodeBindFbo(OnscreenBuffer) glClear(GL_COLOR_BUFFER_BIT)
for (int sample = 0; sample < SampleCount; sample++) { BindFbo(OffscreenBuffer)
vec2 offset = JitterTable[sample]
SetFrustum(LeftPlane + offset.x, RightPlane + offset.x, TopPlane + offset.y, BottomPlane + offset.y, NearPlane, FarPlane)
Render3DScene()
f = 1.0 / SampleCount glColor4f(f, f, f, 1) glEnable(GL_BLEND) glBlendFunc(GL_ONE, GL_ONE)
BindFbo(OnscreenBuffer) BindTexture(OffscreenBuffer) RenderFullscreenQuad() }
|
The key part of Example 6-12 is the blending configuration. By using a
blend equation of plain old addition (GL_ONE, GL_ONE)
and dimming the color according to the number of samples, you’re
effectively accumulating an average color. An unfortunate side effect of jittering is
reduced color precision; this can cause banding artifacts, as shown in
Figure 4. On some platforms the banding
effect can be neutralized with a high-precision color buffer, but that’s
not supported on the iPhone. In practice, I find that creating too many
samples is detrimental to performance anyway, so the banding effect
isn’t usually much of a concern.
Determining the jitter offsets
(JitterTable in Example 6-12)
is a bit of black art. Totally random values don’t work well since they
don’t guarantee uniform spacing between samples. Interestingly, dividing
up each pixel into an equally spaced uniform grid does not work well
either! Example 6 shows some commonly used
jitter offsets. Example 6. Popular jitter offsetsconst vec2 JitterOffsets2[2] = { vec2(0.25f, 0.75f), vec2(0.75f, 0.25f), };
const vec2 JitterOffsets4[4] = { vec2(0.375f, 0.25f), vec2(0.125f, 0.75f), vec2(0.875f, 0.25f), vec2(0.625f, 0.75f), };
const vec2 JitterOffsets8[8] = { vec2(0.5625f, 0.4375f), vec2(0.0625f, 0.9375f), vec2(0.3125f, 0.6875f), vec2(0.6875f, 0.8125f), vec2(0.8125f, 0.1875f), vec2(0.9375f, 0.5625f), vec2(0.4375f, 0.0625f), vec2(0.1875f, 0.3125f), };
const vec2 JitterOffsets16[16] = { vec2(0.375f, 0.4375f), vec2(0.625f, 0.0625f), vec2(0.875f, 0.1875f), vec2(0.125f, 0.0625f), vec2(0.375f, 0.6875f), vec2(0.875f, 0.4375f), vec2(0.625f, 0.5625f), vec2(0.375f, 0.9375f), vec2(0.625f, 0.3125f), vec2(0.125f, 0.5625f), vec2(0.125f, 0.8125f), vec2(0.375f, 0.1875f), vec2(0.875f, 0.9375f), vec2(0.875f, 0.6875f), vec2(0.125f, 0.3125f), vec2(0.625f, 0.8125f), };
|
Let’s walk through the process of creating a
simple app with jittering. Much like we did with the supersample
example, we’ll include a fun transition animation. (You can download the
full project from the book’s website at http://oreilly.com/catalog/9780596804831.) This time
we’ll use the jitter offsets to create a defocusing effect, as shown in
Figure 5.
To start things off, let’s take a look at the
RenderingEngine class declaration and related types.
It’s not unlike the class we used for supersampling; the main
differences are the labels we give to the FBOs.
Accumulated denotes the on-screen buffer, and
Scene denotes the offscreen buffer. See Example 7. Example 7. RenderingEngine declaration for the jittering samplestruct Framebuffers { GLuint Accumulated; GLuint Scene; };
struct Renderbuffers { GLuint AccumulatedColor; GLuint SceneColor; GLuint SceneDepth; GLuint SceneStencil; };
struct Textures { GLuint Marble; GLuint RhinoBackground; GLuint TigerBackground; GLuint OffscreenSurface; };
class RenderingEngine : public IRenderingEngine { public: RenderingEngine(IResourceManager* resourceManager); void Initialize(); void Render(float objectTheta, float fboTheta) const; private: void RenderPass(float objectTheta, float fboTheta, vec2 offset) const; Textures m_textures; Renderbuffers m_renderbuffers; Framebuffers m_framebuffers; // ... };
|
Example 7 also
adds a new private method called RenderPass; the
implementation is shown in Example 8. Note that
we’re keeping the fboTheta argument that we used in
the supersample example, but now we’re using it to compute a scale
factor for the jitter offset rather than a y-axis rotation. If
fboTheta is 0 or 180, then the jitter offset is left
unscaled, so the scene is in focus. Example 8. RenderPass method for jitteringvoid RenderingEngine::RenderPass(float objectTheta, float fboTheta, vec2 offset) const { // Tweak the jitter offset for the defocus effect: offset -= vec2(0.5, 0.5); offset *= 1 + 100 * sin(fboTheta * Pi / 180);
// Set up the frustum planes:
const float AspectRatio = (float) m_viewport.y / m_viewport.x; const float NearPlane = 5; const float FarPlane = 50; const float LeftPlane = -1; const float RightPlane = 1; const float TopPlane = -AspectRatio; const float BottomPlane = AspectRatio;
// Transform the jitter offset from window space to eye space: offset.x *= (RightPlane - LeftPlane) / m_viewport.x; offset.y *= (BottomPlane - TopPlane) / m_viewport.y; // Compute the jittered projection matrix:
mat4 projection = mat4::Frustum(LeftPlane + offset.x, RightPlane + offset.x, TopPlane + offset.y, BottomPlane + offset.y, NearPlane, FarPlane); // Render the 3D scene - download the example to see this code. ... }
|
Example 9 shows the
implementation to the main Render method. The call to
RenderPass is shown in bold. Example 9. Render method for jitteringvoid RenderingEngine::Render(float objectTheta, float fboTheta) const {
// This is where you put the jitter offset declarations // from Example 6-13. const int JitterCount = 8; const vec2* JitterOffsets = JitterOffsets8; glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffers.Accumulated); glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffers.AccumulatedColor);
glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); for (int i = 0; i < JitterCount; i++) { glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffers.Scene); glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffers.SceneColor);
RenderPass(objectTheta, fboTheta, JitterOffsets[i]); glMatrixMode(GL_PROJECTION); glLoadIdentity(); const float NearPlane = 5, FarPlane = 50; glFrustumf(-0.5, 0.5, -0.5, 0.5, NearPlane, FarPlane); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -NearPlane * 2); float f = 1.0f / JitterCount; f *= (1 + abs(sin(fboTheta * Pi / 180))); glColor4f(f, f, f, 1);
glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffers.Accumulated); glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffers.AccumulatedColor); glDisable(GL_DEPTH_TEST); glBindTexture(GL_TEXTURE_2D, m_textures.OffscreenSurface); RenderDrawable(m_drawables.Quad); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); } }
|
Example 9 might give
you sense of déjà vu; it’s basically an implementation of the pseudocode
algorithm that we already presented in Example 6-12. One deviation is how we compute the
dimming effect: float f = 1.0f / JitterCount; f *= (1 + abs(sin(fboTheta * Pi / 180))); glColor4f(f, f, f, 1);
The second line in the previous snippet is
there only for the special transition effect. In addition to defocusing
the scene, it’s also brightened to simulate pupil dilation. If
fboTheta is 0 or 180, then f is
left unscaled, so the scene has its normal brightness. 3. Other FBO EffectsAn interesting variation on jittering is
depth of field, which blurs out the near and
distant portions of the scene. To pull this off, compute the viewing
frustum such that a given slice (parallel to the viewing plane) stays
the same with each jitter pass; this is the focus plane. Yet another effect is motion
blur, which simulates the ghosting effect seen on displays
with low response times. With each pass, make incremental adjustments to
your animation, and gradually fade in the alpha value using
glColor.
|