Scissor Test
Perhaps you've played a game with a split-screen or a mini-map. Or perhaps you recently explored orthographic projections with this renderer:
There's a picture-in-picture display in the bottom-left corner.
Split-screens, mini-maps, and picture-in-picture displays are all achieved by rendering to multiple viewports. Each display's viewport is defined with a call to gl.viewport
. For example, to set up a horizontal split-screen effect, which you might see in a two-player racing game, you might write this code:
const halfHeight = canvas.height / 2;
// gl.viewport's parameters are (x, y, width, height)
gl.viewport(0, 0, canvas.width, halfHeight);
// draw bottom racer
gl.viewport(0, halfHeight, canvas.width, halfHeight);
// draw top racer
const halfHeight = canvas.height / 2; // gl.viewport's parameters are (x, y, width, height) gl.viewport(0, 0, canvas.width, halfHeight); // draw bottom racer gl.viewport(0, halfHeight, canvas.width, halfHeight); // draw top racer
Setting the viewport is usually not enough. If any geometry falls outside the viewport, it will bleed into neighboring viewports. You must perform two additional steps to staunch this bleeding:
- Define the scissor box, which is the rectangle in which you want to restrict the rendering. This will usually be the same as the viewport.
- Turn on the scissor test, which discards any pixels that don't fall within the scissor box.
Applying these steps results in this code:
gl.enable(gl.SCISSOR_TEST);
gl.viewport(0, 0, canvas.width, halfHeight);
gl.scissor(0, 0, canvas.width, halfHeight);
// draw bottom racer
gl.viewport(0, halfHeight, canvas.width, halfHeight);
gl.scissor(0, halfHeight, canvas.width, halfHeight);
// draw top racer
gl.enable(gl.SCISSOR_TEST); gl.viewport(0, 0, canvas.width, halfHeight); gl.scissor(0, 0, canvas.width, halfHeight); // draw bottom racer gl.viewport(0, halfHeight, canvas.width, halfHeight); gl.scissor(0, halfHeight, canvas.width, halfHeight); // draw top racer
The gl.viewport
and gl.scissor
calls use the same parameters, but both are needed because the viewport merely defines the transformation that converts normalized space coordinates into pixel coordinates. It doesn't stop fragments outside the viewport from being written to the framebuffer. The scissor test discards those fragments.