by Naam
wed jan 09 2002
Sub-Surface Scattering? Surely not!

Sub Surface Scattering seems to be a bit of a buzzword nowadays. It has just been implemented in a number of renderers (sadly not C4D's yet) and is especially useful on substances like skin and marble. Now, I was working on an incredibly simple channel shader, actually just a shader that gathers the luminance like a fog shader would do (so not taking the surface normal into account), meaning to find a way to create some kind of a 'peach-skin' appearence. But with certain settings, I get mighty close to a subsurface scatter effect. Below I'll describe the technique and certain shortcomings. Use to your advantage.

What's the idea?


Normally, when a surface's color is calculated, the renderer takes the surface normal (ie. the direction of the surface) into account: it measures the angle between the direction of the light, and the direction of the surface's normal (A). It uses this angle to decide how much light the surface receives on that location. This is all fine and dandy for diffuse surfaces, but what about peach skin?


In the case of peach skin, I figured that, apart from the diffuse shading, there's also a very narrow 'fuzz' layer, receiving illumination which is NOT dependent on surface direction. Instead, it is illuminated like fog, gathering the lighting from all directions directly. I figured I might be able to simulate this by simply writing a shader which uses this type of illumination gathering, as well as slightly displacing the samplepoint to provide for the fuzz to have some depth. When trying this, I got the nice effect of the light creeping along the edges of the object that would normally be in the shadows.


(left to right: no fuzz layer, a (red) fuzz layer of 2m and a (red) fuzz layer of 10m)

Now, this somewhat works, except that the edge of the shadow area is still pretty harsh. So I thought, why don't I take more samples (B), and mix between them?


As shown, I simply took a set of samples along the surface's normal and averaged them. In case of randomized samples, I had to sample both the inside and the outside of the object to get a nice soft edge.


(left: regular spaced samples, right: randomized)

Yup this softens the edge, but it didn't look too peachy to me yet. I decided to try another approach: just sample the illumination of a whole area (C), and see what that does.



(Left to right: increasing the sample area)

More and more, this was beginning to look like the light was actually creeping along the surface of the object. In case of the area samples, I did need more and more samples, and the shader got slower and slower, though for a c.o.f.f.e.e. shader it's actually still quite fast! Above pictures are rendered at about twice the size, with edge AA. Using 20 samples for the shader they render in about 20seconds.

But it's a hack!
So is this Sub Surface Scattering? Not at all! The reason that this approach results in this 'creeping light' effect is because the shader samples a large area of space, and thus the object receives more light when there's less geometry in that area. Also, by using the samples-along-normal approach, and combining it with a fresnel effect, the same simple shader can give you that peach-skin effect that started me on this path.


(Left: area-sampled version. Right: normal-sampled version, combined with a fresnel effect and a 3D noise)

Now of course this technique also has some problems: in the area-sample mode, an object's surface will receive less light if there is another object very near (ie. inside the area). This effect isn't too noticable, but it is present, and not really 'scientifically correct'. Next, when a shadow-area is near to a surface, almost touching it but not quite, the surface does get darkened. Also, shadows falling onto a surface tend to get a smudgy appearence. Quite a nice look actually, but surely not always what you're after. On top of that, the effect can be quite noisy if a large area is sampled with few samples, and the code being c.o.f.f.e.e., it does get slow when lots of samples are needed.


(the sphere gets darkened at the bottom-left edge, even though the shadow isn't touching it. Also note the shadows on the floorplane, which also has the same shader)

Also there are some troubles whe using the illumination-gathering all by itself, because the sampled point of the surface is actually on a flat polygon, not on the smoothed-out surface you seem to see in phong-shading. Some self-shadowing creeps in here as well.


(see?)

Where's the shader? I want it!!
Hold your horses. I just stumbled over these effects today. The shader is still in experimental phase, to see if fits my needs. All values are hard coded, so you can't really use it for anything safe experimenting with the code. Also concider that to make it useable I'd have to triple the amount of code just to build in an interface and make the settings editable and save-able. I'd rather stay off that untill I receive enough requests, or when I decide that it's good enough to actually use in production. So by all means let me know what you think, and if by chance you want to transfer the ideas into C++, I'd be happy to share the code (though I guess after my description it's pretty easy to set it up yourself...)

What else?
I don't know, I guess this was it. If you have any suggestions as to how to solve certain problems, ideas for enhancements, or general comments about anything else, please let me know. For now, I'll just leave you with a few tesrenders below (ordered from worst to best) to show what the different settings do.

samp: number of samples
sampradius:sample radius (both for area and normal methods)
initradius: the offset from the surface at which to take the first sample
usegauss: use gaussion distribution for random function (1=yes, 0=no)
usen: use the surface-normal method (1=yes, 0=no)
userandom: in case of normal method, use a random distribution or not (1=yes, 0=no)
useshadows: no use turning this one off ever ;)