Toon-shading for Oolite
Posted: Thu Jun 11, 2015 12:47 pm
The goal of this post is to share the knowledge I learned while trying to implement Toon shading for Oolite.
And maybe hopefully find help
In this post, I'll stick to generalities and concepts without forcing implementation details on the reader
What is Toon shading? It's applying effects so that the game takes a cartoonish appearance.
Toon shading needs three features : simplified shades, outlines and strong ambient light.
Some games don't draw outlines, like Zelda the windwaker. Others need them because their textures are very detailed, and without outlines it wouldn't feel as a cartoon.
Oolite definitely is in the second category. As a lot of oxps pack a lot of detailed and realistic textures, it is quite better to be able to propose outlines.
Strong ambient light
Ambient light is the light emitted by objects when there is no light source. In the real world, there is none. In 3D scenes, we use them to simplify our work or to do special effects (lightsabers...).
This one is easy to implement, we already have an oxp to control it.
I use a value of 4.0 for my tests.
Simplified shades
Shades in Toon-shading are very basically discretized. For example, on a pixel you may have no shade at all, a shade hiding 50% of the pixel light intensity, a shade of 75%.
I've tested with modified shaders through a test oxp. A shader is a special program for graphical cards. The effect is quite ok, but applying the effect through an oxp to each Entity is maybe not the correct approach.
I need a way to apply the modified shaders to each entity, and at the same time disable oxps ability to impose their own shaders.
I think this is possible relatively easily in Oolite. This would need quite some tests to be sure this doesn't maim any oxp relying heavily on shaders.
Outlines
The is the black outlines around characters and everything you can see in cartoons. It is the hard part. Outlines may be implemented through several methods.
Inverted black scaled envelope
This is the historic approach. One draws a bigger model around one's model, invert and paint black every face. This envelope is only visible where the original model doesn't hide it, around it.
The advantage is it is simple, and works quite well for a given value of well. I've posted some pictures made with this method.
The inconveniences now.
As the model is scaled uniformly, the outline is larger in places further from the model center. For a Cobra Mk3, the “vertical” outlines are a lot thinner than the “horizontal” outlines.
As the model as scaled bigger, this works well for convex models, and badly for others. Our ships are not convex. The Cobra Mk3 has some pipes around its cannon, and the badly placed envelope is visible. Problems are visible for torus stations too.
This method doubles the number of vertices and faces too. A vertex (plural vertices) is one of the 3 points of each face.
I've implemented this solution on a github branch.
Inverted black normal-based envelope
To correct the “convex” problem of the previous method, it is possible not to scale the model, but to move the faces in the direction of the vertices' normals, that is the vector perpendicular to the face at the point of the vertex.
The advantages over the previous method are the thinness of outlines may be controlled better, and it works for convex models.
The inconveniences are the envelope is not continuous, resulting in holes at the junctions between the faces; and the number of vertices and faces is still doubled.
I've implemented this solution, and then discarded it as I couldn't accept the discontinuity.
Blackening depending on the fragments' normals and the viewpoint
A fragment is a point in a face in the currently drawn window. Its characteristics are interpolated from the face's vertices' characteristics, and may be modified by the shaders.
It is possible to draw an outline by calculating if the face is perpendicular to the view of the camera. This works well for smooth models, like a teapot. Unfortunately, it doesn't work well with “hard” models, like our ships.
One big advantage is it doesn't double the number of vertices, faces and fragments.
I've implemented this solution and discarded it.
Post-processing based on normals
The idea here is almost the same as previously. We first render the picture and store the value of the normal vector for each rendered pixel in the window. Then we apply black where the vector is perpendicular to the view of the camera, and/or where the normal is very different from the one of the neighboring pixels. This way, we only calculate once per pixel, instead of once per fragment, which may be a lot, and we can take into account the neighboring pixels.
Post-processing based on depth
The idea here is almost the same as previously. We first render the picture and store the depth value for each rendered pixel in the window. Then we apply black where the depth value is very different from that of neighboring pixels. Simple and easy.
There is an official OpenGL tutorial about this method : http://www.opengl-tutorial.org/intermed ... o-texture/
I didn't yet manage to implement it. I think this is the right way to do Toon-shading outlines for Oolite.
Here, I've finished. Congratulations to have read everything
Any comments or suggestions are very welcome.
And maybe hopefully find help
In this post, I'll stick to generalities and concepts without forcing implementation details on the reader
What is Toon shading? It's applying effects so that the game takes a cartoonish appearance.
Toon shading needs three features : simplified shades, outlines and strong ambient light.
Some games don't draw outlines, like Zelda the windwaker. Others need them because their textures are very detailed, and without outlines it wouldn't feel as a cartoon.
Oolite definitely is in the second category. As a lot of oxps pack a lot of detailed and realistic textures, it is quite better to be able to propose outlines.
Strong ambient light
Ambient light is the light emitted by objects when there is no light source. In the real world, there is none. In 3D scenes, we use them to simplify our work or to do special effects (lightsabers...).
This one is easy to implement, we already have an oxp to control it.
I use a value of 4.0 for my tests.
Simplified shades
Shades in Toon-shading are very basically discretized. For example, on a pixel you may have no shade at all, a shade hiding 50% of the pixel light intensity, a shade of 75%.
I've tested with modified shaders through a test oxp. A shader is a special program for graphical cards. The effect is quite ok, but applying the effect through an oxp to each Entity is maybe not the correct approach.
I need a way to apply the modified shaders to each entity, and at the same time disable oxps ability to impose their own shaders.
I think this is possible relatively easily in Oolite. This would need quite some tests to be sure this doesn't maim any oxp relying heavily on shaders.
Outlines
The is the black outlines around characters and everything you can see in cartoons. It is the hard part. Outlines may be implemented through several methods.
Inverted black scaled envelope
This is the historic approach. One draws a bigger model around one's model, invert and paint black every face. This envelope is only visible where the original model doesn't hide it, around it.
The advantage is it is simple, and works quite well for a given value of well. I've posted some pictures made with this method.
The inconveniences now.
As the model is scaled uniformly, the outline is larger in places further from the model center. For a Cobra Mk3, the “vertical” outlines are a lot thinner than the “horizontal” outlines.
As the model as scaled bigger, this works well for convex models, and badly for others. Our ships are not convex. The Cobra Mk3 has some pipes around its cannon, and the badly placed envelope is visible. Problems are visible for torus stations too.
This method doubles the number of vertices and faces too. A vertex (plural vertices) is one of the 3 points of each face.
I've implemented this solution on a github branch.
Inverted black normal-based envelope
To correct the “convex” problem of the previous method, it is possible not to scale the model, but to move the faces in the direction of the vertices' normals, that is the vector perpendicular to the face at the point of the vertex.
The advantages over the previous method are the thinness of outlines may be controlled better, and it works for convex models.
The inconveniences are the envelope is not continuous, resulting in holes at the junctions between the faces; and the number of vertices and faces is still doubled.
I've implemented this solution, and then discarded it as I couldn't accept the discontinuity.
Blackening depending on the fragments' normals and the viewpoint
A fragment is a point in a face in the currently drawn window. Its characteristics are interpolated from the face's vertices' characteristics, and may be modified by the shaders.
It is possible to draw an outline by calculating if the face is perpendicular to the view of the camera. This works well for smooth models, like a teapot. Unfortunately, it doesn't work well with “hard” models, like our ships.
One big advantage is it doesn't double the number of vertices, faces and fragments.
I've implemented this solution and discarded it.
Post-processing based on normals
The idea here is almost the same as previously. We first render the picture and store the value of the normal vector for each rendered pixel in the window. Then we apply black where the vector is perpendicular to the view of the camera, and/or where the normal is very different from the one of the neighboring pixels. This way, we only calculate once per pixel, instead of once per fragment, which may be a lot, and we can take into account the neighboring pixels.
Post-processing based on depth
The idea here is almost the same as previously. We first render the picture and store the depth value for each rendered pixel in the window. Then we apply black where the depth value is very different from that of neighboring pixels. Simple and easy.
There is an official OpenGL tutorial about this method : http://www.opengl-tutorial.org/intermed ... o-texture/
I didn't yet manage to implement it. I think this is the right way to do Toon-shading outlines for Oolite.
Here, I've finished. Congratulations to have read everything
Any comments or suggestions are very welcome.