Cartoon shader improvements

Status update

I’ve now done some more testing, and fixed a mistake in the last version. It turns out the half-pixel shift was erroneous after all. It causes the detector to trigger only at one of the pixels along an edge, which does make it possible to render one-pixel thin lines, but it has a nasty side effect I didn’t notice before.

Sometimes the outline “floats” at a one-pixel distance from the object, or gets drawn inside it (also at a one-pixel distance from the actual edge). Since the upper/lower object outlines exhibit the opposite behaviours, and their behaviours switch when I flip the sign in the half-pixel shift, this implies that the original version without any shifting is correct. I’m almost tempted to provide the shift as an option, since in some cases it produces nice-looking output, but I’m not sure, since in other cases it produces artifacts.

The supersampling is indeed heavy on the framerate. On the other hand, my GPU is an old Radeon HD 2600 Pro, which is very slow by today’s standards (and running on the even slower open source drivers since the official drivers no longer support it), so the inking may run at decent speeds on more modern hardware.

Comparing by flipping through the images in a viewer, the improvement from the oversampling is significant. I’ll post some screenshots in a separate post (3-attachment limit).

In any case, this means I’m adding an “inking quality” configuration parameter to the final version. (That may be clearer than num_samples, sample_threshold, use_depth_buffer, enable_postprocessing, …)

Also, experimenting with implementing the inker as either inlined into the main postprocessing shader (writing to the final output directly) or as a separate shader rendering into its own texture (and then a couple of lines in the main shader applying the ink texture), the separate pass costs a few fps at least on my machine. But it buys flexibility, since postprocessing for the outlines is not possible if the ink is drawn directly onto the final output.

Multipassing has also some other consequences. It makes the ink separately viewable in BufferViewer (which is nice for debugging); and makes it incompatible with the blur filter, which sees only the colour texture from before any postprocessing filters are applied. But this is an issue in the current design of CommonFilters; it was there already for any multipass filter in 1.8.1. It seems that to do this kind of things properly, CommonFilters needs some sort of mechanism to construct a multipass shader (maybe based on some kind of hardcoded per-shader priority index to determine in which order to apply the filters). I might look into this, but that’s after 1.9.0.

I’m still working on the ink postprocessor. Will try to get it done this weekend. Notes to self:

  • Idea to be tested: in order to add any smoothing ink to a pixel, inked neighbours should already exist along at least two different rows/columns in the stencil. This avoids widening straight horizontal or vertical runs (otherwise pixel row/column aligned runs would bleed). However, it does also affect the look; hence, to be tested.
  • If there are no inked neighbours, or at most one, the original ink pixel should be discarded (and no smoothing applied) to avoid pixel-size ink spots in the output.

It also occurred to me that while the depth buffer by itself is almost useless for inking, it is a useful secondary data source for edge detection. It is good especially for object outlines, and for edges where parts of the same object at different depths have nearly identical normals, as shown:





In the vanilla 1.8.1, note missed internal edge where the tail meets the body. Observe also that the new version detects all the outlines of the dragon.

The drawback is that this kills the framerate even more, because now there are two input textures that both need edge detection with supersampling. But at least now the filter picks up most of the previously remaining missed edges. The only other data sources I can think of are genuine object outlines (maybe not needed, the depth buffer is already pretty good for this?), and material interface edges, both of which require changes to the primary shader generator to generate the necessary auxiliary textures. Currently, only one aux texture is supported, so this may also require some other changes to the infrastructure…

(Indeed, the inker in Blender3D uses several different data sources to find the edges, so I think this hybrid approach is probably correct to obtain high-quality inking.)