Defying physics with shaders

Throughout the development of Rocket Rumble the aim of the Small Jelly design team has been to make the game as physical and diegetic as possible - this focus has extended to the design of the cards.

The gameplay of a typical card game requires cards to be layered on top of one another, so we knew from the offset that our card designs needed be very thin and stackable. We thought that it would, however, be kind of interesting to give the card window itself a certain amount of depth, to make it a 3D cutout in the world, whilst still retaining the traditional card shape and its stackability.

 A working card hand!

A working card hand!

This presented an interesting problem for us an engineering team - this really doesn’t make sense in a 3D physical space. How could we ensure the card was super thin but still allow the window to render into the card in 3D?

  Oops!

Oops!

The first and perhaps most obvious option would be to use a multi-camera setup with the inside of the card on a separate camera. This would have been great except for that at times we have a couple of dozen cards and components on screen at once - having dozens and dozens of cameras isn’t practical for performance reasons, particularly if we consider that we may want to release on less powerful platforms in the future.

Given the limitations of a multi-camera system, we decided to investigate a shader-based approach. We looked at the shaderlab concept of stencils. This allows you to use objects to define a mask layer on a per-pixel basis, which can then be compared in another object to decide which parts of it should render or not - effectively acting as a 3D mask. This was perfect for the problem that we faced.

So, in Rocket Rumble we first render the card window’s housing - we use this as a stencil mask, marking all of the pixels of the housing as “masking”. When we then come to render the component we tell it not to render in areas which are masked by the housing. The card body itself is then on a separate masking layer, meaning that it is not affected by the card housing’s masking.

This results in this awesome 3D effect.

 Much Better!

Much Better!

It reminds me a lot of those magic money boxes that I used to be baffled by as a child where a mirror means that the coin disappears as it goes into the box - defying logic.

Magic money.

If you want to do something similar in your game, take a look at our shader code below. Whilst we’re using it to render cards, the same kind of masking approach gives you a whole lot of flexibility to play with 3D space in other interesting ways. You could for example use it to mimic the Bulb Yoshi effect in Mario Galaxy (link), or perhaps do something even more mind bending like masking a rift in space to create a black hole to another world.

Shader that we use for masking:

Shader "SmallJelly/Mask"
  {
  Properties
  {
       _Layer ("layer", Int) = 1
  }

SubShader
{
       //Defines the stencil
       Stencil
        {
           Ref [_Layer]
           Comp always
           Pass replace
        }

       //Ensures that the mask renders “blank”
       Pass
       {
           ZTest Always
           ColorMask 0
           ZWrite On
       }
   }
   FallBack "Diffuse"
}

Modification to other shaders so that they may be masked (to add before first pass):

Stencil
 {
   Ref [_Layer]
   Comp notequal
 }

If you’d like to find out more we’ve found Unity’s reference documentation on masking to be super helpful, you can check it out here.