Imitating the Canvas Engine (3): The Canvas Effect

Previous Post:
Imitating the Canvas Engine (2): Normal Maps and Depth Blur - Memories of Melon Pan

Now we're at the point where we take the scene and make it look like it's been drawn in one of Welkin's sketchbooks. Honestly, I may have been able to do this with one texture, but when I started this out, I went for two because it was easier. It also makes it easier to illustrate, so there's that.

The first texture I'm working with is obviously going to be the one that gives the screen it's paper-like aesthetics, or as I like to call it, the canvas texture. I made a texture in GIMP, using just a pattern fill and adjusting the brightness and contrast.

Thankfully, the paper pattern that GIMP comes with is mostly shades of grey, meaning it mostly relies on black and white to look like what it does. Since all the color channels are the same, I can just pick any channel I want to use when multiplying colors together.

But let's take a look at the scene before we get into math. It has some regular rendering, plus depth blur.

At first, you might think that you could just multiply the scene color by the canvas color, and maybe even adjust the brightness if things are too dark. But uh... well, just take a look.

Not exactly what we're going for, is it? How can we simulate how color in the real world?

Well, for starters, we could start with our coloring paradigm. See, normally when we talk colors in computers, we talk in reds, greens, and blues. We assume we add them to black, and if you mix all these colors together, we get white.

But remember when you were a kid, and you started playing with crayons? What happened when you colored over blue with green? What happened when you kept changing colors and coloring over the same spot? That's right... you got a toxic sludge of a mess that was a rather unappetizing blackish color. For all that talk about mixing colors that you're spoon-fed when you're in kindergarten, you'd always end up with some filthy sewage lagoon color whenever you tried it with crayons or paints or any other improvised coloring utensil you got your hands on. I've always wondered why.

So here's some food for thought: what happens when you put yellow paint on a white canvas? Obviously, the canvas turns yellow... but instead of saying that we added yellow color to the canvas, can't we also say that the canvas at that spot lost all its other primary colors? If we keep on painting over this with different colors, then eventually the canvas will lose so much color, it will end up being black. Not exactly what happens in the virtual world.

I could go on a bit about the RGB and CYMK color spaces, but suffice to say that if we want to model what happens in the real world, we have to start subtracting color from white, instead of adding color to black.

Cz = (1 - Cs) * (1 - Cc)


For the variables:

  • Cz, the subtractive scene color with the canvas texture applied
  • Cs, the scene color as rendered up to this point
  • Cc, the canvas color at this pixel, from the canvas texture

That will give us what color is subtracted from white, rather than what color is added to black.

Of course, we've converted this to subtractive RGB color, but when we leave the pixel shader and return a color, it has to be in additive RGB color. We could simply take the value of (1 - Cz) and convert it back to additive space. However, this is going to do some weird things - our canvas texture will have the effect of adding brightness to the rendered scene, which will wash out the scene with white.

C = (1 - Cz)
    = (1 - (1 - Cs)(1 - Cc))
    = (1 - (1 + (Cs * Cc) - Cs - Cc))
    = Cs + Cc - (Cs * Cc)


If Cc is in the range [0.0, 1.0],
then C will be in the range [Cs, 1.0].

Hmm... time for a bit of fudging.

Let's say instead, we're going to shade things the other way and darken the scene. How much do we darken the scene by? Well, we already calculated our subtractive color Cz. We can just base how much we darken the scene by the color intensity of Cz, then multiply that with our original scene color. Since Cz is guaranteed to be between 0.0 and 1.0, then the output color will always be darker than the original scene color, if not the same. It's just impossible for it to be brighter.

Ic = ((1 - Cz.r) + (1 - Cz.g) + (1 - Cz.b)) / 3.0
C = Cs * Ic


For the variables:

  • C, final output color
  • Ic, intensity of the canvas texture
  • Cz, the subtractive scene color with canvas texture
  • Cs, original scene color

Well, that looks a whole lot better.

Next Post:
Imitating the Canvas Engine (4): The Frame Effect - Memories of Melon Pan