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 :
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 :
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 :
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 :
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.