20
$\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$
8
  • 1
    $\begingroup$ maybe related: blender.stackexchange.com/questions/88062/… and blender.stackexchange.com/questions/70271/… $\endgroup$
    – user1853
    Commented Aug 30, 2017 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$ Commented Aug 30, 2017 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
    Commented Aug 30, 2017 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
    Commented Aug 30, 2017 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$ Commented Aug 31, 2017 at 8:41

2 Answers 2

21
$\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$
8
  • 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$ Commented Aug 31, 2017 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$ Commented Sep 1, 2017 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
    Commented Sep 1, 2017 at 9:28
  • 1
    $\begingroup$ @Rich Sedman, brilliant work with nodes. Thank you for the file. $\endgroup$ Commented Mar 1, 2019 at 17:35
  • 1
    $\begingroup$ That looks nice! Did Blender come up with a new way of doing that? I mean, when writing this comment, it' barely a few days to Blender 3.0 release, is there a node replacing your very nice code? Maybe something less CPU intense. $\endgroup$
    – SteeveDroz
    Commented Nov 25, 2021 at 17:57
6
$\begingroup$

Prompted by SteeveDroz's comment on Rich's answer, I checked what Blender 3.0 (in fact, 2.83+) can do.

I came out with a non-OSL solution (see the right hand side of the screenshot ↓; the left hand side is Rich's script).

enter image description here

The trick is using the Ambient Occlusion input in "Inside" and "Only Local" mode.

  • However, it's not perfect (see the corner of the "phantom cubes" inside my cube: they are supposed to be sharp but are not).
  • Also, I'm not sure what kind of decay curve it employs, but the result is that you don't have a linear relationship between distance and output, and the scaling you control ends up depending on two parameters: the internal "Distance" node and the multiplying factor you choose to use.

It can be a quickier and built-in workaround if you don't want to use a OSL script. However, I think that the Ambient Occlusion node has similar limiation to the OSL script: it doesn't seem to work on GPUs, at least not mine, and it's based on casting probing rays (so accuracy comes at a cost).


Edit: another image to demonstrate the effect. I'm quite surprised. It only works if OSL (CPU) is enabled

enter image description here

$\endgroup$
2
  • 1
    $\begingroup$ That’s very interesting - thanks for doing that analysis. I wonder what the Ambient Occlusion node is actually doing for volumes… it looks like it could be providing the ambient occlusion at the nearest surface point to that point in the volume - which is presumably why it produces such different results for the cube. $\endgroup$ Commented Nov 26, 2021 at 11:45
  • 1
    $\begingroup$ Interestingly, it only works of "OSL" is checked in the rendering tab $\endgroup$
    – Nicola Sap
    Commented Nov 26, 2021 at 14:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .