Post news Report RSS General cel shader

A blog post about general cel shader used in Labyrinthica: The quest of lima.

Posted by on

Hello,
In a new development blog at www.pompidev.net, I will be making posts relating game development.
I wish to share with you the post about genereal cel shader used in labyrinthica.
The code in this post might be useful for some people.

Labyrinthica features cel shaded graphics.

On the most simplistic level shading is taking a color value from the surface's texture and multiply it by an intensity scalar, which is based on how the light source affect the surface.
The scalar value is usually a floating point that goes from 0 to an unbounded positive number.
In cel shading, we want to quantize this scalar into non continuous values.

The general cel shader is a piece of code that quantize the intensity scalar. The parameters to the function are:
a - The original intensity scalar.
n - The number of quantization cels.
f - The cel transaction factor.
Return value = The quantized intensity scalar.

There are actually n+1 cel levels, the additional cel level is for intensity values greater than 1.
I will not explain the shader in details. I hope the code will be clear enough for people to understand on their own. Though I could explain it in more details if there will be a demand.
A few screen shots with different parameter values:

n = 2, f = 0.8
Cel shade(n=2, f=0.8)

n = 2, f = 0.5
Cel shade (n=2, f=0.5)
n = 3, f = 0.8
Cel shade (n=3, f=0.8)


One last word before the code. My older CelShade function got only one parameter, the intensity.
I made the new CelShade with 3 paramters, but then I saw I would need to update every place I called the function.
Instead I have created GeneralCelShade as the function with 3 parameters, and replaced the old function with a function that calls GeneralCelShade with appropriate parameters.
The bonus is that now changing the parameters in only one place affect the whole game/shaders.
Very simple, yet very useful.

c code:
Quantize (float a, float n)
{
     return floor(a*n)+1.;
}

float
Trunc (float c, float f)
{
     return (c>f)*(c-f)/(1.-f);
}

float
SubCel (float a, float n, float f)
{
     float q = Quantize(a, n);
     float c = q-a*n;
     return (q>1.)*Trunc(c, f)/n;
}

float
GeneralCelShade (float a, float n, float f)
{
     float c = Quantize(a, n)/n;
     a = c-SubCel (a, n, f);
     a = min (a, (n+1)/n)*(3./2.)/((n+1)/n);
     return a;
}

float
CelShade (float a)
{
    return GeneralCelShade (a, 2, 0.8);
}
Post comment Comments
NullSoldier
NullSoldier - - 973 comments

Heh, as a 2D game developer and future game developer I've always wanted to know more about Shaders, what they are, and how they work. I guess the real trick with your shader is how it quantizes your sprites into cells. I'm curious what your non-shaded sprites look like and how it does that.

Reply Good karma Bad karma+1 vote
PompiPompi Author
PompiPompi - - 115 comments

Making a post explaining shaders sounds like a great idea. Thanks :)

I don't understand what do you mean by sprites. The character and the apple you see in the screenshots are not sprites, they are 3D meshes.
The piece of code I posted is only part of the shader, its not the shader itself.

I thought maybe the code I posted is too difficult to understand.
The concept is very easy to understand if you first try to understand what the function do, making SubCel return 0.
If SubCel return 0, you still get cel shading with n+1 cels, but this time there is no smoothing on the edge of the cels.
Subcel just calculate those smoothing between cel edges.

Reply Good karma+1 vote
PompiPompi Author
PompiPompi - - 115 comments

Regarding sprites though, you could do something like this with sprites.
But if you don't intend the sprite to change its shading relative to the position of the light source, then there isn't a great benefit doing that with shaders.
You could just do it offline(or precalculate).

Tell me what kind of an effect you are trying to achieve with your sprites, and maybe I can figure out how to do it with shaders and if they are needed for that.

Reply Good karma+1 vote
NullSoldier
NullSoldier - - 973 comments

No sorry, I just assumed that they were 2D sprites and not 3D meshes.

Reply Good karma Bad karma+1 vote
PompiPompi Author
PompiPompi - - 115 comments

By the way, you can use shaders for all kind of neat effects with 2D sprites.

For instance, you can make a "2.5D" sprite.
That is, you draw a color sprite, which is like drawing a regular sprite.
Only you draw the raw colors and don't draw the shadows.
Then, you draw a height map.
That is a grey values sprite, which is the same as the sprite you have drawen, only each grey color is the height of the sprite relative to the camera. Or the bumpiness of the sprite.
You then translate this height map into a normal map, and you have a sprite that can be affected by light sources like any 3D mesh object.

However, sometimes there is no point bothering doing all these special effect, since a good spriter can make a game look fantastic without all these effects.
But just in case you find these kind of thing interesting.

Reply Good karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: