Get Object Width In Cycles as Value
Open Shading Language

You can enable OSL in the render tab of the properties panel.

Create a text either externally or in Blender, write your
OSL code in that text and save if necessary. In the node
editor, add a script node and select the mentioned text.

Based on the Blender manual using trace in this case is OK.

Simple depth

These shaders need only one trace per sample and get
precise values. Can be used anywhere without any bias.

Normal dependent depth

shader depth(output float depth = 0) {

    if(trace(P, - N))
        getmessage("trace", "hitdist", depth);
}


You can use this code to get the depth along the normal of the surface.



View dependent depth

shader depth(output float depth = 0) {

    if(trace(P, - I))
        getmessage("trace", "hitdist", depth);
}


You can use this code to get the depth from the point of view.

Plugging in the proper function of this depth to a transparent
shader you can create a uniform volume absorption for example.



Stochastic depth

These shaders trace one random direction per sample which average
out over the rendering process. The output of these have to be used
carefully: They can be used to mix colors or shaders and probably
well enough as the roughness of a glass for example. But they can
not be used for a color ramp with lots of color stops for example.

Mathematically: They are unbiased if the resulted color of
the sampled pixel is a liner function of the depth value.

I could add multiple sampling, which would dramatically reduce
the bias for those non-linear cases, but using those would slow
down rendering. I will write those versions if you ask me to.

The random

float random(int seed) {

    return cellnoise(P * 732283.511 + seed * 138257.835);
}


This is a position dependent direct seeded pseudo-random number generator.
Somehow I did not get too much luck with the built-in random function.
Works because the position is already pseudo-random with small variance.

Uniform depth

float random(int seed) {

    return cellnoise(P * 732283.511 + seed * 138257.835);
}

vector hemiRandom(vector direction) {

    vector result;

    int seed = 0;

    do {

        result[0] = random(++seed) - 0.5;
        result[1] = random(++seed) - 0.5;
        result[2] = random(++seed) - 0.5;

    } while(dot(result, result) &gt; 0.25);

    if(dot(result, direction) &gt; 0)
        return result;

    return - result;
}

shader depth(output float depth = 0) {

    if(trace(P, hemiRandom( - N)))
        getmessage("trace", "hitdist", depth);
}


This takes every direction equally into account.

Semi-mathematically: Every direction inside the mesh is sampled with the same chance.

You could considere this as one general view independent depth.



Lambert depth

float random(int seed) {

    return cellnoise(P * 732283.511 + seed * 138257.835);
}

vector lambertRandom(vector direction) {

    vector result = 0;

    int seed = 0;

    do {

        result[0] = random(++seed) - 0.5;
        result[1] = random(++seed) - 0.5;

    } while(dot(result, result) &gt; 0.25);

    result[2] = sqrt(0.25 - result[0] * result[0] - result[1] * result[1]);

    vector a = 0;

    if(direction[0] == 0) {

        a[1] = direction[2];
        a[2] = - direction[1];

    } else {

        a[0] = direction[1];
        a[1] = - direction[0];
    }

    vector b = cross(a, direction);

    return result[0] * a + result[1] * b + result[2] * direction;
}

shader depth(output float depth = 0) {

    if(trace(P, lambertRandom( - N)))
        getmessage("trace", "hitdist", depth);
}


Takes depths towards the normal more into account, the same way a transulcent shader samples the light of its environment.

Mathematically: The density of the random is linear to the dot product of the sample and the normal.

You could considere this as another general view independent depth.



Rough depth

float random(int seed) {

    return cellnoise(P * 732283.511 + seed * 138257.835);
}

vector roughRandom(vector face, vector direction, float roughness) {

    vector result;

    int seed = 0;

    while(1) {

        result[0] = random(++seed) - 0.5;
        result[1] = random(++seed) - 0.5;
        result[2] = random(++seed) - 0.5;

        if(dot(result, result) &lt; 0.25) {

            result = normalize(direction) + result * roughness;

            if(dot(result, face) &lt; 0)
                return result;
        }
    }
}

shader depth(float roughness = 0, output float depth = 0) {

    if(trace(P, roughRandom(N, - I, roughness)))
        getmessage("trace", "hitdist", depth);
}


This is a poor roughness model but could be good enough for your use.

This is general view dependent depth with roughness control.



Nodes

These are the nodes used for the renders: