Cartoon shader improvements

Return to Panda Features in Development

Cartoon shader improvements

Postby Technologicat » Wed Sep 04, 2013 3:46 am

Recently, I had some ideas for improving the cartoon shader, as mentioned here:

viewtopic.php?f=4&t=16329&p=95670#p95506


I now have a working prototype. What I've added is:

- Smoothed light/dark boundaries in light ramping. In addition to sharp thresholding as previously, a linearly interpolated transition is now supported in both single and double threshold modes. The length of the interpolation range can be adjusted (separately for both transitions in double threshold mode). This exploits the continuity of lighting values on smooth surfaces, and is not the proper way to antialias the boundaries under arbitrary circumstances, but in practice it seems to give satisfactory results (at least in my tests).

- Light ramping now optionally affects the specular lighting contribution, too. The threshold/level/smoothing parameters for the specular component can be adjusted separately. New versions of make_single_threshold() and make_double_threshold() have been added to accommodate this. This feature is mainly useful for anime style hair.

- An "advanced ink" filter has been added to CommonFilters.py. It is based on the existing inker, but it has some new features. It accounts for the distance of each pixel from the camera (nearer = larger separation parameter -> thicker line), smooths the lines using a blur filter, and optionally the inking can be rendered into a double-sized buffer to reduce artifacts at nearly horizontal or vertical edges. (This last solution is not completely satisfactory, so I'm not sure yet whether to keep that feature or not.)

- The "cutoff" parameter of Tut-Cartoon-Advanced.py is now integrated into the inker in CommonFIlters.py. It wasn't many lines, and I found it odd that this potentially useful feature was missing. :)


Also, I've fixed the two bugs I found:

- Fixed the double threshold light ramp ( https://bugs.launchpad.net/panda3d/+bug/1214782 )

- Fixed a shader generator error with some material property combinations ( https://bugs.launchpad.net/panda3d/+bug/1219422 )


At this point, I have some questions:

- Are you guys interested in all/some/any of these changes? If some of these changes are considered potentially useful to other Panda3D users, I would like to work toward getting them integrated in the official source.

- What is the proper protocol for posting source code? Attach a patch here?

- In case there is interest, what is the procedure for code review?

- Is there a way to post pictures? I'd like to share some images highlighting the modifications.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby rdb » Wed Sep 04, 2013 5:14 am

Sounds useful! I'd be happy to review and check in your patches, provided that they are backward compatible and they adhere to the Panda coding style. You can use any mechanism you like for submitting patches; a bug report is probably most useful because of the commenting and tracking interface.

I'd love to see some images; you can use the attachment feature of the forums to attach pictures and they will be embedded automatically.
rdb
 
Posts: 9605
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 06, 2013 2:56 am

Thanks for the info. The current code is now posted as a patch against 1.8.1 in the bug tracker:

https://bugs.launchpad.net/panda3d/+bug/1221546

Some of my own thoughts on the current state of the patch:

As mentioned in the original post, I'm not completely satisfied with the double resolution inking, so I'm still debating whether to keep that feature or not. Aside from hardly being a clean solution (and not completely getting rid of artifacts), I haven't yet been able to get the parameters to respond in a visually identical way between the two resolution modes, which would be required to make this option visually independent from the others.


Currently, the blurring (ink line smoothing) of advanced_ink is incompatible with BlurSharpen, because it uses the same blur buffers to render the blur. I have chosen this route, because if I've understood correctly, there is a hardware-dependent limit on the number of supported TEXCOORDn parameters. Since the current "main" shader of CommonFilters (the generated one, rendering to finalQuad) already uses up to 5 (0...4), I didn't want to bump the number used to 7.

A general solution would be to either reuse the current buffers or use separate ones, depending on whether both advanced_ink and BlurSharpen are enabled at the same time. This would enable both to work at the same time if enough buffers are available. But then the logic becomes more complicated, which increases the risk of bugs and makes future maintenance harder.

But wait? Maybe an even more general, and much simpler, solution would be to keep a list of allocated TEXCOORDns, and assign them dynamically to the different parameters of the shader when the full rebuild runs. This would keep their allocation optimal, and would be easy to implement in Python. It seems the only bookkeeping required is to match the TEXCOORDn numbers between the generated vshader and fshader. I could do this if it's useful?


As for the other stuff in the improvements, I think it is pretty much feature-complete, but of course feedback is welcome.

Usage examples are still missing, as I've been testing this using a short test program of my own (related to the project I'm building). For the final version, I could prepare a cleaned-up "advanced basic" tutorial demonstrating how to use the added features.


Finally, I have one further idea that I haven't tested yet, which I might still add before a final version. I'm thinking that the light/dark boundaries could be smoothed by a postprocessing pass, instead of smoothing them in lighting space like the currently proposed patch does. The lighting space smoothing has its uses however, and would be kept as a separate feature.

(For example, the Toon shader in Blender has separate diffuse/specular controls, and smoothing parameters for each. The current patch pretty much implements that for Panda. Blender's "size" corresponds to the threshold value (larger size = lower threshold), and "smooth" to the smoothing value.)

For this purpose, I'm thinking of introducing another blur filter that detects pixels which have no normal map discontinuities, and blurs only those. What would happen, theoretically, is that in an area with a constant lighting value (generated by the light ramping), the result is a no-op, while on the light/dark boundaries applying a blur will smooth the boundary.

This would slightly blur the textures, but that is unavoidable in this approach. Objects shouldn't bleed, because their outlines usually have a normal map discontinuity with whatever is behind them. The only case where this approach obviously fails is when a light/dark boundary is aligned with an edge in the mesh. I'll need to test this to see how common that is.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby rdb » Fri Sep 06, 2013 5:12 am

Sounds great! It's indeed a good idea to have a small function that returns a TEXCOORD$ semantic that hasn't been used yet, so that it uses consecutive registers regardless of which filters are enabled.
rdb
 
Posts: 9605
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 06, 2013 7:29 am

rdb wrote:Sounds great! It's indeed a good idea to have a small function that returns a TEXCOORD$ semantic that hasn't been used yet, so that it uses consecutive registers regardless of which filters are enabled.


Ok, I'll add this to the patch.

In the meantime, here are some images.


01_sharp_inking_no_filter.png
Sharp inking with no blur, for comparison.
01_sharp_inking_no_filter.png (86.16 KiB) Viewed 1468 times

Sharp inking with no blur, for comparison.


02_smoothed_inking.png
Smoothed inking with smoothing parameter set to 0.5.
02_smoothed_inking.png (114.02 KiB) Viewed 1468 times

Smoothed inking with smoothing parameter set to 0.5.


05_depth_dependent_inking.png
Depth-dependent inking (thicker line near camera).
05_depth_dependent_inking.png (154.17 KiB) Viewed 1468 times

Depth-dependent inking (thicker line near camera).
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 06, 2013 7:35 am

EDIT: new screenshots showing more clearly what the patch does.

This is for a character builder I'm working on. The hair is procedurally animated using a custom physics simulation (see later post).

Here is an array for visual comparison, in 1:1 size to show artifacts clearly.

Upper left (reference): autoshader toon shading from vanilla 1.8.1
Upper right: smoothing of outlines and light-to-dark transitions enabled
Lower left: specular quantization enabled
Lower right: both smoothing and specular quantization enabled
cartoon_shading_smooth_vs_nosmooth.jpg
Overview of changes in patch.
cartoon_shading_smooth_vs_nosmooth.jpg (100.12 KiB) Viewed 337 times


The outline and light-to-dark transition smoothing options can be controlled separately. Same layout again, but with light-to-dark transition smoothing disabled:
cartoon_shading_nosmooth.jpg
Light-to-dark transition smoothing off.
cartoon_shading_nosmooth.jpg (102.38 KiB) Viewed 337 times


Again, but now all four rendered with light-to-dark transition smoothing enabled:
cartoon_shading_smooth.jpg
Light-to-dark transition smoothing on.
cartoon_shading_smooth.jpg (98.43 KiB) Viewed 337 times

The parameters are as follows. Lighting:
Code: Select all
thresh0=0.55
lev0=0.8
smooth0=0.03
affect_specular=True
thresh0_specular=0.5
lev0_specular=0.8
smooth0_specular=0.03

Inking:
Code: Select all
separation=1.2
cutoff=0.5
color=(0,0,0,1.0)
use_advanced_ink=True
blur_amount=0.5
depth_enabled=True
Last edited by Technologicat on Fri Sep 12, 2014 6:25 am, edited 1 time in total.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 06, 2013 6:54 pm

Finally, I have one further idea that I haven't tested yet, which I might still add before a final version. I'm thinking that the light/dark boundaries could be smoothed by a postprocessing pass, instead of smoothing them in lighting space like the currently proposed patch does.

Tested. This idea was in fact useless.

It didn't work, because when viewed from far away, e.g. a character's face might no longer have sharp edges, so it gets all blurred. Also, objects may actually bleed a bit, because both the pixel inside the object boundary and its neighbor outside it trigger the edge detector. For inking, this doesn't matter, but for blurring...

Thresholding parameters could be added, like for inking, but I think that smoothing of the light/dark boundaries shouldn't require that much careful tweaking from the user. The lighting space approach is cleaner in at least two ways - the smoothing range is a more transparent parameter to configure, and that approach doesn't require a postprocessing filter (leaving more buffers and registers for other filters).


I've also added the TEXCOORDn allocator thingy, and now any combination of filters can be enabled at the same time (as long as the hardware has enough registers).

An updated patch will follow shortly.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 06, 2013 7:13 pm

Patch updated. New version in bug tracker.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 12, 2014 6:30 am

As mentioned in passing concerning the new screenshots, the custom hair simulation code is now working.

See thread with screenshots here: viewtopic.php?f=9&t=17208
Last edited by Technologicat on Fri Sep 12, 2014 8:03 am, edited 1 time in total.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Fri Sep 12, 2014 6:37 am

Ping for code review or any comments from the community.

Cartoon shader improvements:
https://bugs.launchpad.net/panda3d/+bug/1221546

Bugfixes related to cartoon shading:
https://bugs.launchpad.net/panda3d/+bug/1214782
https://bugs.launchpad.net/panda3d/+bug/1219422

Please observe also that with both diffuse and specular quantized, the lighting obtains three "levels" like discussed (in 2010) by Anon and ninth in viewtopic.php?t=7439 even when a single threshold light ramp is used. Three levels can be obtained also by using a double threshold light ramp on the diffuse component only, but the visual styles produced by these two approaches are different.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby rdb » Fri Sep 12, 2014 8:06 am

Just letting you know I certainly haven't forgotten about this. It's on my list of things to look at before the 1.9.0 release.
rdb
 
Posts: 9605
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Re: Cartoon shader improvements

Postby Technologicat » Mon Sep 15, 2014 6:56 am

rdb wrote:Just letting you know I certainly haven't forgotten about this. It's on my list of things to look at before the 1.9.0 release.

Ok. Thanks :)

Is there an approximate timeframe for 1.9.0?

Namely, there are a couple more things I'd be interested in fixing/adding, if I have the time:

1) Some last-minute fixes to the cartoon shader. There is still something funky going on with the inking, as can be seen in the screenshots.

  • The alpha gradient in the outline of the side bangs, in places where they are rendered in front of the head, looks as if shaded backwards (causing visibly jaggy edges). I didn't change the normal map based edge detection logic, and indeed, the artifact is visible both in the original and improved versions.
  • The detector doesn't seem to like rendering horizontal edges at those places where the object's outline meets the default background (empty space), although edges with a significant vertical component (when viewed in the screen plane) render just fine.
Under most circumstances, the edge detector seems to work correctly, so this may be a shortcoming of the algorithm. But I need to investigate this to be sure.

2) Character hair physics simulation, if deemed useful enough to include into Panda. At least one other community member is interested in having such functionality.

This one must be ported to a C or C++ extension and slightly expanded before it becomes practically useful. An initial usable version will probably take a few days of coding, depending on how easy or difficult it is to add new modules to the Panda build system. (I've had a look at your comments in http://www.panda3d.org/forums/viewtopic.php?t=3414, the manual https://www.panda3d.org/manual/index.php/Interrogate, and the skel example that comes with the Panda sources - but haven't yet had time to try creating my own module.)

3) I think it would be interesting to integrate ninth's procedural lens flare filter ( https://www.panda3d.org/forums/viewtopic.php?t=15231 ) into CommonFilters. The updated logic in the cartoon shader patch should make the integration slightly easier. The filter looks spectacular, requires no special setup for the scene, and at least I think it would make an especially nice addition to the standard filter set. (It goes well with bloom, as both are psychovisual tricks intended to increase the perceived dynamic range.)
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby rdb » Mon Sep 15, 2014 9:27 am

I'm trying to release it this month, but given the amount of work, it might bleed into next month. That means there's not much room for big new features, but there is for bug fixes. If you know more information about any of the bugs you mentioned, please post it in the bug tracker.

The lens flare effect looks cool. If someone finds a way to integrate this into CommonFilters.py, I'd be happy to ship it. Have you asked ninth about the license it is under, and/or whether or not we'd be allowed to include it as part of Panda?

As for hair simulation: it sounds a bit specific to be included in a general-purpose library like Panda, but if the extension is well-written and if it's interesting to enough people for it to be of general use, I'd be happy to include it as a contributed module.
rdb
 
Posts: 9605
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Re: Cartoon shader improvements

Postby Thaumaturge » Mon Sep 15, 2014 10:37 am

Looking at the screenshots, I'm not sure that the outline-smoothing is an improvement: to my eye the steps of the original jagged outline are still rather visible, and the blurring seems to largely have the effect of making the outlines fuzzy.

(I'm sorry to say that I don't have a better option to suggest, however. I'm far from an expert in graphics techniques, and while I've been thinking on-and-off about something similar the only option that I've come up with is supersampling, which seems likely to be expensive. :/)

That said, I do rather like the specular quantisation and the light-to-dark transition.
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Re: Cartoon shader improvements

Postby Technologicat » Mon Sep 15, 2014 7:30 pm

rdb wrote:I'm trying to release it this month, but given the amount of work, it might bleed into next month. That means there's not much room for big new features, but there is for bug fixes. If you know more information about any of the bugs you mentioned, please post it in the bug tracker.

Ah, that soon!

At the moment I don't have any more information, but I'll return to this after I investigate a bit.

rdb wrote:The lens flare effect looks cool. If someone finds a way to integrate this into CommonFilters.py, I'd be happy to ship it.

I've looked at the code and I think it shouldn't be that hard to integrate. The tricky part is figuring out any interactions with other filters. But I can look at this some time in the near future, as this is a feature that I'd like to have.

rdb wrote:Have you asked ninth about the license it is under, and/or whether or not we'd be allowed to include it as part of Panda?

Not yet. Reading the thread, it seems he was fairly liberal about it, but you're right that we have to ask first. I'll do that.

rdb wrote:As for hair simulation: it sounds a bit specific to be included in a general-purpose library like Panda, but if the extension is well-written and if it's interesting to enough people for it to be of general use, I'd be happy to include it as a contributed module.

Ok.

In a way, it is indeed specific considering the spectrum of things a general 3D library can do, but on the other hand, I think modelling human characters is a fairly common task in game development.

Even in big-budget games, to my knowledge properly animated long hair seems to be a rather new phenomenon, and is still rather rare. Off the top of my head, I can think of Sophie's ponytails from Tales of Graces f (2012), Esther's braid from Ni no Kuni (2011/2013), and some of the hairstyles in LittleBigPlanet (both 1 (2008) and 2 (2011)), which is heavily physics-based, but that's about it. Dragon's Dogma (2012) tried with some shoulder-length hairstyles, but the simulation looked somehow off. If I recall correctly, the original Hitman's tie was procedurally animated, but at that time (2000) the trend didn't seem to catch on.

If the option to animate hair (pretty much) automatically is available, this gives greater freedom in character design, as the hairstyle question is no longer a question of a significant amount of additional programming.

So, that's my reason for asking :)

I'll go ahead with implementing the C version for now.

Thanks for your input on all of the points!
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Mon Sep 15, 2014 8:04 pm

Thaumaturge wrote:Looking at the screenshots, I'm not sure that the outline-smoothing is an improvement: to my eye the steps of the original jagged outline are still rather visible, and the blurring seems to largely have the effect of making the outlines fuzzy.

I have to admit I'm not perfectly satisfied with it either.

However, maybe there's also a component of taste. My eye tends to be drawn to sudden contrasts occurring over a single pixel - the kind of artifact that is produced when drawing lines without any antialiasing. I'm not so much disturbed by slight blur. The original version did not have any antialiasing, so it had the sudden contrast everywhere. The outline-smoothing version removes this artifact in most of the outlines drawn, at the cost of adding a blur to the outlines.

Granted, the jagged edges of the side bangs look horrible (as they did before any changes). I'll have to re-check the code one more time to figure out what is going on in this particular case.

Thaumaturge wrote:(I'm sorry to say that I don't have a better option to suggest, however. I'm far from an expert in graphics techniques, and while I've been thinking on-and-off about something similar the only option that I've come up with is supersampling, which seems likely to be expensive. :/)

I did try double-resolution inking (and then downsampling the result) in an earlier version of the patch, but ended up discarding that, as it didn't look any better. Going higher than double resolution probably makes no sense for a fullscreen texture.

The problem that prevents proper antialiasing is that the inking filter has no concept of a line: it simply colours pixels depending on local information only. It looks for discontinuities in the normal map (to my understanding this is a standard inking technique).

At each pixel, it first gathers the normal data at four points: (-dx, 0), (+dx, 0), (0, -dy) and (0, +dy) relative to the current pixel. Here dx = dy is the offset parameter. The differences between the smallest and largest values of the x and y components of this normal data are then summed and compared to a threshold. This produces a number, which in the original filter was used to switch the ink on or off (for that pixel). In the modified filter, the hard on/off has been changed to a linear interpolation (with saturation) - under the assumption that "less discontinuity" should mean a more translucent line.

The blur used for the outlines is a selective one, which only adds more ink to pixels (never removing existing ink). The intent is to simulate the antialiasing that would be applied while drawing a line, but without knowing there is a line. The drawback is that long horizontal or vertical line segments will bleed. This may contribute to the fuzzy appearance.

So, I don't have a better solution either - ideas from the community are welcome :)

Thaumaturge wrote:That said, I do rather like the specular quantisation and the light-to-dark transition.

Thanks!

And thanks for your input!
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Thaumaturge » Mon Sep 15, 2014 8:46 pm

Hmm... Actually a kernel of a thought does come to mind, inspired by something that I saw elsewhere, but I'm not at all sure that it would work, or perform well.

The idea is to perform two passes of a form of edge detection: one detecticing vertical runs of "inked" pixels and one detecting horizontal runs. (If both can be done in a single pass then all the better.) The trick would be to determine directionality: if one could determine the "origin pixel" for the run--that is, the first "ink" pixel along the line of the run--then one could add "ink" pixels along the horizontal and vertical runs, fading along the length of the run.
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Re: Cartoon shader improvements

Postby Technologicat » Tue Sep 16, 2014 5:33 am

Thaumaturge wrote:Hmm... Actually a kernel of a thought does come to mind, inspired by something that I saw elsewhere, but I'm not at all sure that it would work, or perform well.

The idea is to perform two passes of a form of edge detection: one detecticing vertical runs of "inked" pixels and one detecting horizontal runs. (If both can be done in a single pass then all the better.) The trick would be to determine directionality: if one could determine the "origin pixel" for the run--that is, the first "ink" pixel along the line of the run--then one could add "ink" pixels along the horizontal and vertical runs, fading along the length of the run.

Maybe some version of this would be worth a try. Would you happen to have any links, or pictures? I'm not sure I understood in which pixels the ink should be applied e.g. along a horizontal run.

The ink is rendered into a separate texture anyway, so it is easy to postprocess it separately.

Implementation-wise, this kind of algorithm sounds a bit tricky for the GPU. As you may know, the fragment shader program is a kernel that will be executed independently for each pixel in the output texture. Any pre-existing data fed in to the shader can be utilized in the computation, but ultimately the fragment shader must decide the output colour of each pixel independently, without knowledge of any other pixels being rendered in the same pass. This makes the computation "embarrassingly parallel".


Maybe the best approach would after all be some kind of supersampling. Looking at what the inker would like to do, is somehow represent the location of the ink at a resolution higher than the target - i.e. subpixel rendering. From this perspective it is clear why it is difficult to smooth the lines in a postprocessing pass: the subpixel information is not present.

Which gives me an idea. It occurred to me just now that I haven't yet tried doing supersampling properly, i.e. inside the inking algorithm. Instead of using the center of the pixel as the reference position for the offsets, we could repeat the calculation four times, using shifted positions that are off the center, some way toward each corner. Then we colour the pixel based on how many of these calculations returned that the pixel should be inked (using a hard on/off threshold in the subcalculations).

This is probably a better way to utilize subpixel information than simply doubling the resolution of the ink texture and downsampling. It still relies on linear interpolation of the 1x resolution normal map, so I'm not sure how well this will work. But at least it is worth a try.

(As I see it, the only practical way to get a higher-resolution normal map would be to render the whole scene at 2x resolution - which makes no sense in a realtime application.)
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Tue Sep 16, 2014 12:28 pm

Subpixel inking tested. Result below. Specular quantization and light-to-dark smoothing enabled so that the only option that changes is the inking type.

Comments? Better or worse than the previous attempt? Update the patch to use this?

Left: vanilla 1.8.1 inking
Right: subpixel inking (with the same settings)

subpixel_inking.jpg
Subpixel inking.
subpixel_inking.jpg (47.66 KiB) Viewed 273 times

Observe the ponytail holder and the outermost outline. Note also changes in internal outlines between the ponytail bunches (at the right in the image) and the edgemost front bang at the left edge of the image.

Beside adding subpixel sampling, I also made two slight changes to the computation:

  • The original inker used only the first two components from the view normal map, while this version uses all three. This was the reason behind some of the missing outlines (note the chin).
  • The ink calculation now uses an internal half-pixel shift, using the center point of the pixel as the reference. This visibly improved the results especially for the problematic side bangs in the picture.
It's obvious that the red channel represents x (and the scaling is 0...255 for -1.0...1.0), but I'm still not sure which way the green and blue channels in the aux texture map to the y (up/down) or the z (in/out) directions in the view space (and what is the possible range for z, since backward pointing faces are usually not rendered). This is something that could use more documentation. (I was trying to find out whether a component, and which one, needs to be weighted twice as much as the others. Currently they all use the same weight.)

As for the side bangs, they're somewhat of a challenge to ink. The problem is that there simply is not much variation in the view space normal where the two surfaces meet. Maybe a more advanced version could use a separate texture to identify different objects or materials, and ink those outlines too. (This would require some nontrivial changes to the main shader generator.)

One more thing about the subpixel approach. To eliminate noise, the code uses a voting method, where at least any 3 of the 9 subsamples must decide to ink the pixel before any ink is applied. The amount of ink is then linearly interpolated from 3 votes = very translucent to 9 votes = replace pixel with specified cartoon inking color. This improved the output slightly (again, especially in the side bangs) when compared to an initial version which did not use voting.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Thaumaturge » Tue Sep 16, 2014 2:22 pm

I do think that it's an improvement--at the least (as you point out) it inks edges that the previous version missed, and it lacks the blurriness of that version. There's still some jagginess, but there are regions in which it's softening the lines somewhat.

I hadn't realised that you're using the normals for your inking; the outlining that I'm doing at the moment is for a pencil-shader, and does pretty much what you seem to be suggesting: since I don't care about the colour of the object for the final output, I give each object a unique colour, then render colour and normals. The normals are used for lighting and thus the actual pencil-shading (with a bit of contribution to the outlines), and the colour is used for edge-detection, with changes in sample colour identifying edges.

Maybe some version of this would be worth a try. Would you happen to have any links, or pictures? I'm not sure I understood in which pixels the ink should be applied e.g. along a horizontal run.

Here's a quick mock-up of what I had in mind:
edges.png
edges.png (22.37 KiB) Viewed 266 times

The result isn't perfect: it lacks an understanding of the intention behind the edge, and the in the final image might be better with a small multiplier applied to the "run-length", causing the pixel-value to fall off more quickly.

Implementation-wise, this kind of algorithm sounds a bit tricky for the GPU. As you may know, the fragment shader program is a kernel that will be executed independently for each pixel in the output texture. Any pre-existing data fed in to the shader can be utilized in the computation, but ultimately the fragment shader must decide the output colour of each pixel independently, without knowledge of any other pixels being rendered in the same pass. This makes the computation "embarrassingly parallel".

Indeed, this is the problem that I keep hitting, and a significant reason to doubt that my suggestion would likely work. :/

If there were some way of keeping state I do think that it could be made to work; moving the processing over to the CPU should enable that, but that seems likely to be very slow...

The only other thing that comes to mind is switching from a pixel-based approach to the inking to a geometry-based approach: before rendering, determine the "outline" edges and place cards (or a Meshdrawer-style trail) along them, drawing an appropriate texture along them.
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Re: Cartoon shader improvements

Postby Technologicat » Wed Sep 17, 2014 2:52 am

rdb wrote:The lens flare effect looks cool. If someone finds a way to integrate this into CommonFilters.py, I'd be happy to ship it. Have you asked ninth about the license it is under, and/or whether or not we'd be allowed to include it as part of Panda?

Ninth replied in the lens flare thread. He said he'd be happy if the filter was made a part of Panda. As for the license, he said "something like WTFPL or Public Domain" so it's definitely compatible.

I'll look into integrating the lens flare as soon as I'm finished with the last-minute inking changes.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Wed Sep 17, 2014 3:23 am

Thaumaturge wrote:I do think that it's an improvement--at the least (as you point out) it inks edges that the previous version missed, and it lacks the blurriness of that version. There's still some jagginess, but there are regions in which it's softening the lines somewhat.

I think the rest of the jagginess is probably a limitation of the 1x resolution normal map. The screenshot is basically saying that in the jaggy areas, there is not enough normal map discontinuity outside the "jags" to warrant inking the pixel.

I played around with the settings in my code last night and surprisingly, it turns out that the two "minor changes" seem to have a larger effect on output quality than the subpixel sampling. The subpixel sampling does help visibly (especially in problematic areas), but the half-pixel shift in the discontinuity detector and the use of all three components of the normal already bring a lot of improvement by themselves. I think I'll make the number of samples configurable, as it affects performance.

Thaumaturge wrote:I hadn't realised that you're using the normals for your inking; the outlining that I'm doing at the moment is for a pencil-shader, and does pretty much what you seem to be suggesting: since I don't care about the colour of the object for the final output, I give each object a unique colour, then render colour and normals. The normals are used for lighting and thus the actual pencil-shading (with a bit of contribution to the outlines), and the colour is used for edge-detection, with changes in sample colour identifying edges.

That sounds nice.

My inker is a modified version of the inker in vanilla 1.8.1, which also used normals. Also, using the normals plays well with the existing shader generator infrastructure - the currently available textures are the final pixel colour (which in the general case cannot be used for edge detection), the depth buffer (which I think I tested and found useless for inking), and the normal map.

Thaumaturge wrote:Here's a quick mock-up of what I had in mind (...)

Ah, thanks! Now I see. Looks like the result could be quite nice.

And it indeed seems that this is nearly impossible to do on the GPU, at least unless there is some clever trick I haven't thought of.

But this gives me another idea. What about if we instead look for short (e.g. max. 5 pixels) horizontal/vertical runs locally near each pixel? The "run length coordinate" would not be available (as it's inherently a global piece of information), but it would be possible to smooth selectively based on the number of inked neighbours (and their placement).

This would do something like the subpixel inking already does, but ignoring the normal map and just filtering in the pixels neighbouring the generated lines.

It may either look blurry, or useful - I'll have to test.

Thaumaturge wrote:If there were some way of keeping state I do think that it could be made to work; moving the processing over to the CPU should enable that, but that seems likely to be very slow...

Yes.

State could be saved into a new texture in a multipass approach, but I think the actual computation of the desired information is the problematic part, as it relies on stepping along a varying-length run (which is basically a serial approach). Parallelization tends to cause things like this :/

Thaumaturge wrote:The only other thing that comes to mind is switching from a pixel-based approach to the inking to a geometry-based approach: before rendering, determine the "outline" edges and place cards (or a Meshdrawer-style trail) along them, drawing an appropriate texture along them.

That's one possibility I hadn't thought of. Could be nice. However, it's a lot of re-engineering and I'm not yet that familiar with Panda, so maybe not for 1.9.0 :)
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Thaumaturge » Wed Sep 17, 2014 3:09 pm

I look forward to seeing what your experiments produce! ^_^

My inker is a modified version of the inker in vanilla 1.8.1, which also used normals. Also, using the normals plays well with the existing shader generator infrastructure - the currently available textures are the final pixel colour (which in the general case cannot be used for edge detection), the depth buffer (which I think I tested and found useless for inking), and the normal map.

Ah yes--I don't think that I've actually looked at what the Shader Generator does. ^^;

(To be honest, I'm somewhat new at working with shaders; I have some (albeit limited) understanding of their workings, but have very little practical experience in working with them.)

But this gives me another idea. What about if we instead look for short (e.g. max. 5 pixels) horizontal/vertical runs locally near each pixel? The "run length coordinate" would not be available (as it's inherently a global piece of information), but it would be possible to smooth selectively based on the number of inked neighbours (and their placement).

Hmm... That seems as though it could work--I'd likely be worried about all of those texture lookups (if I'm understanding you correctly), but then I imagine that supersampling does something very similar anyway. At the least it does seem worth a shot. ^_^

A thought: could it be improved by implementing the run-detection as a binary search? instead of checking each sample point in a given direction, test the maximum distance, and if that's clear, test halfway to it, and so on. That might allow you to test longer runs than an iterative approach--but I don't know whether it would be feasible in a shader.

That's one possibility I hadn't thought of. Could be nice. However, it's a lot of re-engineering and I'm not yet that familiar with Panda, so maybe not for 1.9.0 :)

Oh yes, it would be a completely different approach, with a thoroughly different set of components and challenges!
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Re: Cartoon shader improvements

Postby Technologicat » Thu Sep 18, 2014 4:36 am

Thaumaturge wrote:Ah yes--I don't think that I've actually looked at what the Shader Generator does. ^^;

If you want to take a look, the primary shader generator in Panda is written in C++ and resides in /panda/src/pgraphnodes/shaderGenerator.cxx. The interesting stuff happens in the method synthesize_shader(). For cartoon shading, the relevant part is the light ramping.

The postprocessing shader generator is Python-based, located at /direct/src/filter/CommonFilters.py. This generator takes care of the inking step (search for CARTOON_BODY). (In Linux, CommonFilters.py is installed in /usr/share/panda3d/direct/filter/. This is useful for prototyping, as you can simply replace the file and re-run your program.)

When I started this, I found the shader generators rather understandable after reading and puzzling over the source for a while.


Thaumaturge wrote:(To be honest, I'm somewhat new at working with shaders; I have some (albeit limited) understanding of their workings, but have very little practical experience in working with them.)

Actually, me too. It's just something that appeared pretty similar to scientific computing :)

Many of the elements are the same: mathematics (especially numerics and vectorized calculations), algorithm speed considerations (number of operations and memory fetches, degree of parallelism), and the code consists mainly of (sometimes very long and logically unsplittable) functions that are designed to perform one task well. Unlike in application programming, the logic is usually so simple that you can work out just by reading the source code whether a given implementation works correctly or not (resorting to pen and paper for the math).

Still, that leaves a lot to learn regarding things that you gradually pick up by exposure to a particular field - for example, I'd never even imagined a procedural lens flare, until ninth posted his shader and the link where the technique is explained.

(The same blog (http://john-chapman-graphics.blogspot.co.uk/) contains some more useful stuff, including an explanation of SSAO, how to do realtime motion blur, and how to generate good-looking spotlight cones for dusty air. There's no index, but it's quickly read through as there are only 7 entries in total.)

The approximate technique used by ninth is a good find, as it's simple to implement and understand, and it's computationally light. Another approach to generating procedural lens flares is by raytracing the lens system. See e.g. http://resources.mpi-inf.mpg.de/lensflareRendering/pdf/flare.pdf. The results look very impressive, and the authors mention that the raytracing can be done in a vertex shader, but the article was light on details, so this won't be at the forefront of my list of things to try :P


Thaumaturge wrote:Hmm... That seems as though it could work--I'd likely be worried about all of those texture lookups (if I'm understanding you correctly), but then I imagine that supersampling does something very similar anyway. At the least it does seem worth a shot. ^_^

Yes, it will involve additional texture lookups, as indeed does the supersampling. But it might allow for using less supersamples.

In the case of supersampling, it would be possible to eliminate some lookups by placing the supersamples so that e.g. the "right" detector point of one supersample coincides with the "left" detector point of the next one, but that drastically reduces the flexibility of supersample placement and makes the code more complicated. And I'm always worried about using regular grids for sampling (due to potential for moire artifacts).

There are also special tricks that are sometimes applicable. For example, it is good to keep in mind that the GPU does bilinear filtering at no extra cost. Hence, one should keep an eye out for places in the algorithm where a linear combination of two neighboring texels can be used to derive the result instead of using the original two values directly. In the context of gaussian blur, see http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ (link picked from inside the lens flare link posted by ninth).


Thaumaturge wrote:A thought: could it be improved by implementing the run-detection as a binary search? instead of checking each sample point in a given direction, test the maximum distance, and if that's clear, test halfway to it, and so on. That might allow you to test longer runs than an iterative approach--but I don't know whether it would be feasible in a shader.

If it runs on the GPU, probably not. It's true that the binary search reduces the run time for one instance from O(n) to O(log(n)), but the branching is likely to destroy parallelism.

A general guideline is to avoid if statements in a shader, as they can be slow in Cl. It wasn't said explicitly, but I think that implies that the GPU performs SIMD type tasks well, while branching requires extra effort. At least from a vectorization viewpoint that makes sense.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Thaumaturge » Thu Sep 18, 2014 11:52 am

Thank you for the links! There could be some useful stuff in there. ^_^

Yes, it will involve additional texture lookups, as indeed does the supersampling. But it might allow for using less supersamples.

Ah, I see. Well, I look forward to seeing what comes of it. ^_^

If it runs on the GPU, probably not. It's true that the binary search reduces the run time for one instance from O(n) to O(log(n)), but the branching is likely to destroy parallelism.

Ah, fair enough; I'll confess that some of the restrictions inherent in shaders can be frustrating sometimes (such as discovering that--on my machine, at least--variable array indexing isn't allowed). :/

Thank you for pointing out this particular issue, by the way: my pencil shader uses nested if-statements at one point, which I now may want to change.
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Re: Cartoon shader improvements

Postby Technologicat » Thu Sep 18, 2014 3:50 pm

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:
05_vanilla.jpg
Vanilla 1.8.1 (comparison)
05_vanilla.jpg (71.78 KiB) Viewed 206 times

06_with_depthbuffer.jpg
9 x supersampling. Depth buffer as auxiliary data source.
06_with_depthbuffer.jpg (66.33 KiB) Viewed 206 times

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.)
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Thu Sep 18, 2014 3:53 pm

Screenshots - vanilla vs. 9 x supersampled

Another view of the dragon from the Panda cartoon shading tutorial, using vanilla 1.8.1 and supersampled (subpixel), depth buffer enabled inking.

01_vanilla.jpg
Vanilla 1.8.1
01_vanilla.jpg (73.09 KiB) Viewed 206 times

04_supersample_9.jpg
9 x supersampling. Depth buffer as auxiliary data source.
04_supersample_9.jpg (69.22 KiB) Viewed 206 times
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Thu Sep 18, 2014 4:05 pm

Screenshots - effect of supersampling

This post concludes the status update.

All screenshots in this post taken using the new version. Depth buffer is used as an auxiliary data source.

The depth buffer is also used to slightly modulare the separation parameter (changing the thickness of the line depending on distance from camera), but I might still tweak this. Due to the nature of the algorithm, separation does not really control line thickness: instead, it controls the radius of the edge detection stencil.

Up to one pixel of separation, these are effectively the same thing, but above one pixel, it also displaces the outline from the pixel containing the edge, because the edge is detected from further away. An object that is thinner than the separation value may get several non-overlapping "ghost" outlines. (This is a consequence of the inking algorithm, and was there already in 1.8.1.)

The only difference between the pictures in this post is the number of subpixel samples. Look at the top of the head to see the effect clearly.

02_with_depthbuffer.jpg
No supersampling.
02_with_depthbuffer.jpg (72.7 KiB) Viewed 206 times

03_supersample_5.jpg
5 x supersampling.
03_supersample_5.jpg (69.77 KiB) Viewed 206 times

04_supersample_9.jpg
9 x supersampling.
04_supersample_9.jpg (69.22 KiB) Viewed 206 times
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Technologicat » Thu Sep 18, 2014 5:26 pm

And now for the reply:

Thaumaturge wrote:Thank you for the links! There could be some useful stuff in there. ^_^

Np :)

Thaumaturge wrote:Ah, fair enough; I'll confess that some of the restrictions inherent in shaders can be frustrating sometimes (such as discovering that--on my machine, at least--variable array indexing isn't allowed). :/

Sometimes limitations like this can be worked around. For example, many older GPUs (mine included) do not support variable length for loops (the Cg compiler wants to unroll loops, and cannot if the end condition depends on a variable).

If you have a shader generator, and the loop's end condition uses a variable just because it depends on a configuration parameter (which remains constant while the shader is running), you can make the shader generator hardcode it from its configuration when it writes the shader. If you're coding your application in Python, Shader.make() (from pandac.PandaModules) comes in useful for compiling shaders generated at runtime. Look at CommonFilters.py for usage examples. But of course doing this adds another layer of logic.

Also, keep in mind that error messages from Cg can sometimes be misleading. I encountered the variable length for loop problem when I was trying to figure out why Panda's SSAO wouldn't run on my GPU. I was debugging entirely the wrong thing until rdb stepped in, said he'd seen a similar situation before, and that it is likely the problem is in the variable-length loop, not the array it is indexing into (although the error message indicated that the problem should have been in array indexing).

(SSAO is fixed in 1.9.0, using the approach mentioned above.)

Thaumaturge wrote:Thank you for pointing out this particular issue, by the way: my pencil shader uses nested if-statements at one point, which I now may want to change.

Just after I said that, I did some testing this evening and found that the following run at the same speed on my GPU:

Code: Select all
if(samples > CUTOFF)
  o_color = lerp(o_color, k_targetcolor, (samples - CUTOFF) / (NUMSAMPLES - CUTOFF));

vs. the branch-free alternative

Code: Select all
float f = step(CUTOFF, samples);
o_color = (1.0 - f)*o_color
        + f*lerp(o_color, k_targetcolor, (samples - CUTOFF) / (NUMSAMPLES - CUTOFF));

but on the other hand, this was the only if statement in the shader. When I later complicated this to

Code: Select all
if(samples1 > CUTOFF)
  o_color = lerp(o_color, k_targetcolor, (samples1 - CUTOFF) / (NUMSAMPLES - CUTOFF));
else if(samples2 > CUTOFF)
  o_color = lerp(o_color, k_targetcolor, (samples2 - CUTOFF) / (NUMSAMPLES - CUTOFF));

vs. the branch-free equivalent

Code: Select all
float f1 = step(CUTOFF, samples1);
float f2 = step(CUTOFF, samples2);
o_color = (1.0 - max(f1,f2))*o_color
        + f1*lerp(o_color, k_targetcolor, (samples1 - CUTOFF) / (NUMSAMPLES - CUTOFF))
        + (1.0 - f1)*f2*lerp(o_color, k_targetcolor, (samples2 - CUTOFF) / (NUMSAMPLES - CUTOFF));

the alternatives still ran at the same speed. Of course, this test is hardly conclusive; the texture lookups in the supersampler are probably taking so much time that a single if statement (or two) has a negligible effect on the total time taken by this particular shader. But that's also a useful piece of information: branching is not always a total performance killer.

I also observed that the Cg compiler, at least as invoked by Panda, seems to optimize the code (which is of course the sensible thing to do - what is not clear a priori is whether there is an optimizer in any given compiler, and if so, what kinds of optimizations it applies).

The optimizer seems pretty advanced - it seems to do some kind of dependency analysis and omit code that does not affect the output. (I was trying to do a rudimentary kind of manual profiling of the shader, disabling parts of it to see what is taking the most time.)

Namely, even if the shader code analyzes both the normal and depth textures, there is absolutely no speed impact if it does not use the result (filling o_color with a constant value). The expected performance hit from the texture lookups appears immediately when the result of the calculation is used in computation of o_color. I disabled the if statement and the lerp, too, using just "samples/NUMSAMPLES" to set the red component of o_color, setting the other components to constant values. The result was the same.

In conclusion, it might be good to test your particular case using both the nested-if and branch-free approaches, if the branch-free version is not too complicated to write.
Technologicat
 
Posts: 90
Joined: Tue Aug 20, 2013 11:48 pm

Re: Cartoon shader improvements

Postby Thaumaturge » Thu Sep 18, 2014 7:27 pm

Regarding the new version: I do think that it's an improvement over the 1.8.1 version--the reduction in missed edges alone is enough to make it a worthwhile inclusion, methinks.

One thing that I notice: the edges in the new version seem to be a lighter colour than in the 1.8.1 screenshots--is that intentional?

Hmm... Looking at the lines, the jaggedness of the edges does seem reduced. Looking closely, the antialiasing pixels seem a bit light--could they be deepened a bit, to make the antialiasing a little stronger?

If you have a shader generator, and the loop's end condition uses a variable just because it depends on a configuration parameter (which remains constant while the shader is running), you can make the shader generator hardcode it from its configuration when it writes the shader.

I don't think that this will likely help in my case--as you mention a little further on in your post, there's a variable-length loop involved. In short, I was experimenting with using a count of sample colours (which are effectively object ids in my implementation, recall) when detecting edges in order to antialias my lines somewhat--the idea being that a point the samples of which are heavily biased towards one colour or another is presumably further from the edge than one that has a nearly even distribution, and can thus be rendered as "partially-inked", hopefully shading the line a little.

In conclusion, it might be good to test your particular case using both the nested-if and branch-free approaches, if the branch-free version is not too complicated to write.

Hmm... Perhaps... I might go back and have another shot at a non-if version (the nested-if version replaced a non-if version that wasn't working); it will likely be cleaner, at any rate.
MWAHAHAHAHA!!!

*ahem*

Sorry.
User avatar
Thaumaturge
 
Posts: 1164
Joined: Sat Jun 07, 2008 6:34 pm
Location: Cape Town, South Africa

Next

Return to Panda Features in Development

Who is online

Users browsing this forum: No registered users and 0 guests