Cel Shading a la Wind Waker?

Blitz3D Forums/Blitz3D Programming/Cel Shading a la Wind Waker?

Cubed Inc.(Posted 2014) [#1]
I've recently been practicing cel shading effects in Blitz3d using a blended sphere map applied to models that need apply; however, the technique I'm using has the side effect of the 'shading' always moving with the camera, where as in Wind Waker, the 'shading' stays opposite of the light source.
I don't really care to have the shading texture constantly move opposite of the light source, however. I just want to make a shading texture that wraps the model totally regardless of the model's texture coordination while at the same time remaining independent from the camera's rotations. The shading texture will then just be 'set' opposite the set main light source.
Anyone have any ideas on how to pull this off? Any help is appreciated.


Kryzon(Posted 2014) [#2]
This is very easily done with shaders, but Blitz3D does not have that.

I tried implementing the software method in this article :
http://web.archive.org/web/20070223171132/http://www.gamedev.net/reference/programming/features/celshading

Although it's a slow method, it works. Right now it uses a single directional light, so the position of the light doesn't change the shading, only its orientation.
This means that you should rotate this light to a direction where it shades objects like the sun, and leave it like so.



Download the code and media here:
https://www.mediafire.com/?5bnxy24s6tydxkm

EDIT: The following is just the code.




Cubed Inc.(Posted 2014) [#3]
I heard somewhere that the same effect could be achive via applying a constantly updated sphere map. Does this ring a bell, maybe?


Kryzon(Posted 2014) [#4]
Have you tried that sample?


Yasha(Posted 2014) [#5]
I can't post a sample right now but what you probably want to investigate is a cube map texture.

A cube map is, in a lot of ways, a more complicated and powerful version of a sphere map; by providing six different "fronts", the texture can be aligned towards any arbitrary point in 3D space, not just directly towards the camera (this is what makes them handy for water reflections, their most common use). So all you have to do is have the right cube map texture and align it towards a light source, and bam; same effect as the sphere map but correct.

Cube maps were not in the original (1.6X) version of B3D. Make sure your compiler and docs pack are updated to the latest version and they should become available. There are no packaged examples, sadly.

You can generate the texture in several ways, either pre-painted with hard lines or by putting a camera inside a lit sphere and taking renders of each side, or things like that.

Will post working code later when I'm at my computer.


Kryzon(Posted 2014) [#6]
While it can't be done with a sphere map because it's always computed relative to the camera (MSDN reference), I realized that it is possible to reproduce this effect with a 'diffuse mode' cubemap, if you rotate an inverted, flat-shaded cube to face the camera and render this cube in the cubemap.

I believe that this should be faster than the earlier approach of manually altering the vertex UVs, since the cubemap operations would be done internally by Blitz3D and you only need to render the cubemap once per frame and apply it to numerous meshes at the same time.

This method still uses a single directional light.
You can use this for a fixed "sun" type light, or like WindWaker where you shade the character based on the light that's closest to him (reference).

This method is simpler in that it does not need any external media, only what you use for your game. The shading is generated by the program.
It's also easy to change the colour of the shading - you have to change the colour of the "shading cube" used to render the cubemaps.





EDIT: This must be what Yasha is referring to.
I would argue that the cubemap is always static: It's always aligned to the world axis. It's the "content" that you're rendering that needs to be oriented to serve your purpose.
Furthermore, if you apply the cubemap in its default "specular" mode, it only works to a certain extent - it leaves edges of the mesh unshaded since the cubemap is sampled based on the reflection of the normals of the vertices in view-space with the vector from the camera to each vertex.
If you use it with the "diffuse" mode, however, you are simply wrapping the mesh with the faces of the cubemap, so the shading covers all parts of the mesh. The normals of the vertices are directly used to sample the cubemap.


Cubed Inc.(Posted 2014) [#7]
Absolutely astounding techniques posted here. Just a small question, however: would it be possible to just create a custom cubemap and apply it at the right settings to any entities that need provided?


Yasha(Posted 2014) [#8]
Yep Kryzon is right, you do need to re-render the cubemap each frame (haven't worked with B3D in a while, was confusing myself).

However, don't let this put you off: a few low-res square renders of the inside of a cube (or sphere) aren't going to break the millisecond bank, and will be much faster than looping over vertices CPU-side for all but the simplest meshes (as well as working with animated meshes). In many situations - such as outdoor scenes, where the sun is at infinity and therefore the lighting is all parallel - you can also re-use the same render for many or all objects being shaded, meaning that the technique can potentially scale very well indeed.

(Also Kryzon's example is much tidier than mine, so I won't waste space by posting it now.)


Kryzon(Posted 2014) [#9]
@Yasha: Code from you would never be a waste of space.

Would it be possible to just create a custom cubemap and apply it at the right settings to any entities that need provided?

If you're certain that you want a fixed direction for the shading (as in, the infinite sun that Yasha mentioned), you can create the cubemap once and assign it to all the meshes that you want to have the effect - you would assign the cubemap as the top-level texture on each of the cel-shaded meshes so that it multiplies (darkens) the textures below.
In this case, you would not need to render to the cubemap again, and can leave it as it is.

If you're interested in this fixed-direction-shading approach, you can also save the cubemap directly as a bitmap file so that it is easier to use the next time: You load that bitmap file as a texture with the cubemap flag (128).
Out of curiosity, I exported the cubemap texture that I used in that second sample. It uses two shades (light and dark), and with a 45° degree light source it looks like this:



By adding a third shade with MSPaint (based on the cube faces, you can imagine where the third shade should be), you get this:



The code to prepare an arbitrary mesh to use one of the cubemap textures above would be the following:
Const FLAG_COLOUR	= 1
Const FLAG_MIPMAP	= 8
Const FLAG_CUBEMAP	= 128 

Const FX_FULLBRIGHT	= 1

Const CUBEMODE_DIFFUSE	= 2

Const BLEND_MULTIPLY	= 2 ;In this mode the dark parts of the texture darken the mesh. The bright parts do nothing.
Const BLEND_MULTIPLY_2	= 5 ;In this mode the light parts of the cel-shade will brighten the mesh and the dark parts darken. Average parts (coloured 128) do nothing.

ClearTextureFilters()

Global celShade =	LoadTexture( "Cel_Shade_3_Shades.png", FLAG_COLOUR + FLAG_MIPMAP + FLAG_CUBEMAP )
			SetCubeMode( celShade, CUBEMODE_DIFFUSE )
			TextureBlend( celShade, BLEND_MULTIPLY ) ;Also try the "Multiply 2" mode.	

Local diffuseTexture	=	LoadTexture( "Character1.png", FLAG_COLOUR )

mesh	=	LoadAnimMesh( ... )
		EntityFX( mesh, FX_FULLBRIGHT ) ;Cel-shaded meshes are always "fullbright."
		EntityTexture( mesh, diffuseTexture, 0, 0 )	;Layer 0.
		EntityTexture( mesh, celShade, 0, 1 )		;Layer 1.
If you leave those shades as monochrome, the colouring should come from the diffuse texture of the mesh since it'll be blended with the cubemap.
But if you want to use coloured shades, which is recommended if you want to convey a coloured light source, you can simply use the bucket-fill tool in MSPaint to tint the shades.
You can also only tint the shadow, and leave the lighter shades as monochrome (this reproduces the second sample, with that blueish shadow).
In any case, a coloured cubemap makes your mesh look like this:




Cubed Inc.(Posted 2014) [#10]
Thank you so much, guys! You're all super helpful!


fox95871(Posted 2014) [#11]
You could probably do pseudo cell shading based on the code for antialiased meshes. Break up the dolphin into separate pieces, make them all black, slightly bigger, and ordered behind the colored mesh. Then make it so the further away from the camera they are, the bigger they get, so the outlines appear to stay the same size.

http://www.blitzbasic.com/Community/posts.php?topic=82090