11
$\begingroup$

I would like to create a material that has a volumetric component whose local Density depends on the depth of each volume point below the surface.

Can the information "how deep below the surface is a given point" (i.e. the intensity of red color in the figure below) be obtained directly from the material nodes, without adding extra geometry or modifiers?

enter image description here

I've tried using the Z component of the UV Texture Coordinate (after having assigned an UV map), and other kinds of node setups, but I couldn't find a way to obtain that quantity.


Another way of looking at it: consider that SSS can be seen as a Volume Scatter with a Density decreasing smoothly from the surface to one skin-thickness below, while an abrupt fall-off (density goes suddenly from one to zero) corresponds to a Volume Scatter applied on a mesh with a Solidify modifier.

I would like to be able to create any generic falloff profile, including these two.

$\endgroup$
  • 1
    $\begingroup$ maybe related: blender.stackexchange.com/questions/88062/… and blender.stackexchange.com/questions/70271/… $\endgroup$ – cegaton Aug 30 '17 at 18:09
  • $\begingroup$ Use a Point Density texture using either the vertices (if they're dense enough) or a particle system emitted from the faces. $\endgroup$ – Rich Sedman Aug 30 '17 at 18:57
  • $\begingroup$ @RichSedman thanks for the suggestion, however it would require extra data at the Object level (a particle system) or extra conditions on the mesh (evenly distributed vertices, while I'd like to apply this to any manifold surface) $\endgroup$ – Nicola Sap Aug 30 '17 at 19:30
  • $\begingroup$ @cegaton thanks for these links. They are quite specific (the first is bound to either constant density or SSS-defined falloff, the second requires an a priori knowledge - and a possibly easy analytical description - of the mesh's shape), but at least they help me understand that probably there is no easy way to obtain this information given the available node tools. Hopefully somebody will like to differ! $\endgroup$ – Nicola Sap Aug 30 '17 at 19:34
  • 1
    $\begingroup$ There is no such functionality in Cycles yet, your only option is OSL. The reason is that this requires raytracing inside shader evaluation, which is bad for gpu performance as Cycles is implemented. That's also the reason why there is no AO shader with value output (only with closure output), no true "pointiness" shader but a fake one or no rounding of edges at render time. These all require some coding to be done to Cycles (or some OSL coding). $\endgroup$ – Jaroslav Jerryno Novotny Aug 31 '17 at 8:41
15
$\begingroup$

You can use an OSL shader that measures the proximity of the mesh from within a volume. It works by projecting out rays from each point in the volume to probe the surrounding mesh. Be warned that this will be extremely CPU intensive as it has to trace multiple rays from each point within the volume which is significantly extra CPU work.

Here's the shader in action, varying the emission color based on depth :

vary emission color based on depth

Here's the code :

shader volume_meshproximity(
    vector Point = P,
    int Iterations = 1,
    output float Min_Distance = 0.0,
    output float Max_Distance = 0.0,
    output float Avg_Distance = 0.0
    ) {

    float x;
    float y;
    float z;
    vector rand;
    float scale = 5000.0;
    float sum_dist = 0.0;
    float max_dist = 0.0;
    float min_dist = 0.0;
    float Distance = 0.0;

    for (int loop = 0; loop < Iterations; loop++)
    {

        rand = noise(Point*scale,loop*scale);
        x = rand[0] - 0.5;
        y = rand[1] - 0.5;
        z = rand[2] - 0.5;

        if(trace(Point,normalize(vector(x,y,z))))
            getmessage("trace", "hitdist", Distance);
            //getmessage("trace", "N", Normal);
            //getmessage("trace", "hit", Hit);

        if (loop == 0)
        {
            sum_dist = Distance;
            max_dist = Distance;
            min_dist = Distance;
        }
        else
        {
            sum_dist = sum_dist + Distance;

            if (Distance > max_dist)
            {
                max_dist = Distance;
            }

            if (Distance < min_dist)
            {
                min_dist = Distance;
            }
        }
    }

    Max_Distance = max_dist;
    Min_Distance = min_dist;
    Avg_Distance = sum_dist / Iterations;
}

To use it, make sure you're using a version of Blender compiled to include OSL support and enable the Open Shader Language checkbox in the Render properties.

Create a new Text block and paste the above code. Name it something like 'surfaceproximity.osl'.

In your Node Editor, add a Script block and select the 'surfaceproximity.osl' text as its source. This should give you a node like this :

osl node

The 'Iterations' control how many 'probe rays' are projected out from each volumetric point - more rays will provide slower renders but sharper results (since it will sample more internal geometry of the mesh). Point is the location in World space for the origin of the probe rays (pass the Object texture coordinate through a Vector Transform node to transform the point from Object to World coordinates). The resultant 'Min', 'Max', 'Avg' are the minimum detected distance (ie, the closest point) the maximum detected distance (the furthest away point) and the average distance of all probe rays (gives an indication of how 'enclosed' that part of the volume is (a thin section of mesh will tend to have a smaller average, a wide open section will have a larger average - when compared to the min and/or max).

The 'Min_Distance' can be used to indicate the depth from the surface of the mesh. For example, it can be used to control the density of volumetric Scatter/Absorption to give Suzanne an internal skeleton as follows :

suzanne skull material

Here the OSL shader is set to project 16 probe rays (Iterations) from each point within the volume. This controls the density of the Volumetric Scatter such that closer than 0.1 blender units (the Greater Than node) it is transparent (density 0) whereas further in than 0.1 blender units it is opaque (density 30). An Emission shader makes the 'skin' of Suzanne surrounding the 'skull' visible to show the effect. Adjusting the 0.1 in the Greater Than node will vary the thickness of the 'skin' (ie, the depth at which the 'skull' begins).

This produces the following results :

suzanne skull

Blend file attached.

Similarly, you could use a Less Than node to create a 'Solidify' effect (the inverse of the above example) or drive the Volume density directly from the output of the OSL node to vary the properties smoothly - as mentioned in the original question.

$\endgroup$
  • $\begingroup$ Thank you for writing this code! I will try it later. What did you connect your "Point" input to? $\endgroup$ – Nicola Sap Aug 31 '17 at 15:44
  • 1
    $\begingroup$ I connected it to Object texture coordinate. Pretty sure it needs to be in World coordinate space so you might need a Vector Transform node to translate it if your mesh isn't at the origin or is rotated/scaled. I will iron out any such issues and update the answer later. $\endgroup$ – Rich Sedman Aug 31 '17 at 15:57
  • 1
    $\begingroup$ @NicolaSap I've ironed out a couple of bugs from the code and added an example (to give suzanne a skull based on depth below the surface of the mesh as a demonstration) along with a Blend file to download. I'll try and put together an example using a smooth rather than abrupt fall-off of density - although that's more difficult to visualise than a definite edge. $\endgroup$ – Rich Sedman Sep 1 '17 at 0:21
  • 1
    $\begingroup$ I haven't used it yet, but from you examples it looks exactly like I wanted, so I'll accept it straight away. Thank you! $\endgroup$ – Nicola Sap Sep 1 '17 at 9:28
  • 1
    $\begingroup$ @Rich Sedman, brilliant work with nodes. Thank you for the file. $\endgroup$ – Rita Geraghty Mar 1 at 17:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.