Is there a distance-based texture for Blender?

I was wondering if there is a texture similar to VRay’s DistanceTex available for Blender:

It basically lets you texture an object by having other objects intersect with it (or just get near it, depending on the radius you define), and set colors for inside, outside, near and far ranges. I use it quite often in Max (with VRay) to add text, panel lines or other stuff onto an object’s surface, without having to worry about UVs, texture resolution or having to deal with double geometry (usually you just make the objects driving the texture not renderable). It can also be plugged into the bump slot, so that your text or panel lines are bumped into the surface.

1 Like

This would be very nice to have in Blender. There’s a Geometry node in Cycles, but I think you can only measure the distance between the outer limits of the object that has the material applied to it.

You can use the geometry node, combined to a color ramp, for example for using only one dimension like X :

But you can also use the distance by using the Math Vector node in distance mode, to the point (0., 0., 0.) (or any other point, by the way) :

See you :slight_smile: ++
Tricotou

2 Likes

Kinda related, but you can also shade one object based on the angle towards another object:

Another solution : you can use the dynamic paint modifier & vertexcolor

1 Like

Yeah, I think none of those recreates what I’m after. Looks like the geometry node is only using a single point for shading, not the distance of the actual object surfaces to each other.

And vertex colors are obviously too low-res for this approach.

Edit: Looking up the Dynamic Paint, this might work for what I want to achieve. I’ll give this a try. Maybe it’ll be good enough to bake out the necessary maps. Thanks for that!

Still, a proper distance node with more parameters would be awesome.

2 Likes

2 Likes

Closer, but still it’s only using the distance of the pivot to the surface. It should work more like AO, between surfaces.

That’s not so straight forward as it seems… You can use the AO node, but it will use the median distance from hit points. You probably want the minimum distance instead, and for that we don’t have any specific node.
However it’s possible to use OSL for tracing rays and use the minimum distance…

void rng_seed(output int rng, int seed)
{
  int chash = seed;
  if (chash == 0) chash = 1;
  rng = chash * 891694213;
}

float rng_uniform(output int rng)
{
  float res = rng / float(2147483647) * 0.5 + 0.5;
  rng *= 891694213;
  return res;
}

void to_unit_disk(float x, float y, output float x_out, output float y_out)
{
  float r, phi;
  float a = 2.0 * x - 1.0;
  float b = 2.0 * y - 1.0;
    
  if(a > -b) {
    if(a > b) {
      r = a;
      phi = M_PI_4 *(b/a);
    }
    else {
      r = b;
      phi = M_PI_4 *(2.0 - a/b);
    }
  }
  else {
    if(a < b) {
      r = -a;
      phi = M_PI_4 *(4.0 + b/a);
    }
    else {
      r = -b;
      if(b != 0.0) phi = M_PI_4 *(6.0 - a/b);
      else phi = 0.0;
    }
  }
  x_out = r * cos(phi);
  y_out = r * sin(phi);
}

void make_orthonormals(vector N, output vector a, output vector b)
{
  if(N[0] != N[1] || N[0] != N[2]) a = cross(vector(1, 1, 1), N);
  else a = cross(vector(-1, 1, 1), N);
  
  a = normalize(a);
  b = cross(N, a);
}

vector sample_cos_hemisphere(vector N, float randu, float randv)
{
  vector T, B;
    
  make_orthonormals(N, T, B);
  to_unit_disk(randu, randv, randu, randv);
  float costheta = sqrt(max(1.0 - randu * randu - randv * randv, 0.0));

  return randu * T + randv * B + costheta * N;
}

shader Distance(
  float Distance = 0.2,
  int Samples = 8,
  int Backface = 0,
  output float Fac = 0 )
{
  int i, rng;
  float f, randu, randv, ray_t, hits = 0;
  vector ray_P, ray_R;
  float Dist;
  float FDist = Distance;

  f = fmod(cellnoise(P*123456.0), 1.0);
  rng_seed(rng, int(f * 2147483647));
  
  for(i = 0; i < Samples; i++) {
    randu = rng_uniform(rng);
    randv = rng_uniform(rng);
    
    ray_P = P;
    ray_R = sample_cos_hemisphere(N, randu, randv);
    ray_t = Distance;
    if (Backface > 0){ ray_R = -ray_R;}    
    if(trace(ray_P, ray_R, "maxdist", ray_t)){
        getmessage ("trace", "hitdist" ,  Dist );
    }else {
        Dist = Distance;
    }
    
    FDist = min(FDist, Dist);
  }
  Fac = FDist/Distance;
}
5 Likes

Wow, thanks, that looks like you put a lot of work into it! But how exactly can I use it? I’ve selected the script it in a Script node, and pluggd the output factor into a Mix node for the Base Color, but I only get a 50/50 mix of both colors. This is how it looks (in Cycles, obviously):

Am I doing it wrong?

EDIT: Nevermind, I missed activating OSL in the render settings. Thanks so much for this!

Now that it’s working though, I still have a few questions:

Right now, as soon as I make the influencing object invisible, it doesn’t create the texture anymore. Would it be possible to make this work even if the object is invisible?

Also is there any way to select which object (or even better: which collection) is included in the calculation, so that only the objects in the selected collection are creating the effect? (even the object using the texture is influencing itself, which becomes obvious when activating “backface”)

1 Like

Use a Transparent BSDF for the invisible object. This way the render will store the object in the BVHtree.

Unfortunately, we still don’t have an implementation of ‘traceset’… But a workaround is possible:

/* Other functions here */

shader Distance(
  float Distance = 0.2,
  int Samples = 1,
  int Backface = 0,
  string Object = "",
  output float Fac = 0 )
{
  int i, rng;
  float f, randu, randv, ray_t, hits = 0;
  vector ray_P, ray_R;
  float Dist;
  float FDist = Distance;
  string geom;
  f = fmod(cellnoise(P*123456.0), 1.0);
  rng_seed(rng, int(f * 2147483647));
  
  for(i = 0; i < Samples; i++) {
    randu = rng_uniform(rng);
    randv = rng_uniform(rng);
    
    ray_P = P;
    ray_R = sample_cos_hemisphere(N, randu, randv);
    ray_t = Distance;
    if (Backface > 0){ ray_R = -ray_R;}    
    if(trace(ray_P, ray_R, "maxdist", ray_t)){
        getmessage ("trace", "geom:name", geom);
        if (geom == Object){      
            getmessage ("trace", "hitdist" ,  Dist );
        } else {
            Dist = Distance;
        }
    }else {
        Dist = Distance;
    }
    
    FDist = min(FDist, Dist);
  }
  Fac = FDist/Distance;
}

Now there’s a string input in the node… just put the object name you want to look up. It’s possible to have a group of objects/collection, but it’s more code. It might be easier to joint all objects into 1 mesh.

2 Likes

you can also do some black magic with the vector transform mode and some math functions for texture mapping when it comes to translating between object and world space for math functions.

This method also works nicely if you want to use vertex colors to control a texture mask

But you can use the empty to also scale/rotate/move textures as well but I do sometimes keep a hackish multiplier in there for easy texture scalling.

2 Likes

That works really well, fantastic!

I assume the usual object picker found in other nodes isn’t available in OSL scripting? I mean, it doesn’t really matter… putting a string in there works just as well. To bad it looks like strings don’t support wildcards, otherwise defining several objects through * or ? would be possible. That way you could just have all relevant objects end in _distance, and call them via *_distance. Anyway, collapsing them works just as well.

Setting the other object to Transparent BSDF works perfectly, although I’ve now noticed that the shader only registers the distance around faces, and not the area that is encapsulated by the object. I guess I’ll just combine it with an AO node with a distance of 0 to get that, but it would be a nice option to include in the shader (granted, I have no idea if that is even possible… the shader would need to recognize if the object is closed. An alternative would maybe be separate frontface/backface distances?

Also I’ve noticed that an object only influences the map, if it’s actually intersecting with the textured geometry. Is this to keep calculations down? Because technically even an object that’s not intersecting the textured object can be inside the “distance” range, and thus should (optionally) influence the map.

Don’t get me wrong though, I’m really grateful that you’ve done this.

@joseph_raccoon:
That’s a really cool effect as well. Not exactly what I was looking for, but it could come in handy as well, thanks!

Not atm… at least in Cycles. We need metadata for the shader parameters…
IIRC, Appleseed does use metadata on their nodes; but I’m not very familiarized with this engine.

It’s possible, but needs more of code. OSL even has regexp functions, so it’s just a matter of using those instead of my simple string to string comparison.
For example, instead of «if (geom == Object){» you can have «if (endswith(geom, Object)){», and you can use ‘_distance’ as suffix on your objects.

more on this in the OSL specs manual, Ch. 7.8

well… that’s just a matter of checking if the trace hits a Backside face or not, and if it does, distance would be set to 0 or something else… It might happen that the trace don’t hit any backside face (if it’s not within the distance radius), so a slightly different approach is needed. (by tracing longer distances, and discarding all hits that aren’t backfacing and inside the distance radius)
If the object is open, it’s not up to the shader to care. If you give the wrong objects to engine, don’t expect the correct image to come out.

Actually it doesn’t need any intersection, just for the object to be inside the Distance radius (either above or below the surface, not both).
As I adapt the script from another that was doing AO, I left the option for separating the tracing above the surface from below the surface (hence the Backface input), but it’s also easy to change.

1 Like

At the current state it’s already working quite well, so thanks again!

Should you decide to keep developing this further, a few extra features would make this map even better. Since my original request was based on the features of VRay’s Distance Texture, here’s a screensot of that shader’s interface:

The slots marked with a red dot are the ones that can be driven by a texture, but I think we would only need this for the distance value, because the rest can just as well be achieved via a Mix node. The two “separate” switches are used to activate the use of a different “near”-color inside and outside the object, overriding the “near”-color selected at the top. Furthermore the two “solid” switches are used to activate a complete fill inside and outside the object, using the two colors defined in the “inside color” and “outside color” slots. The bottom list is of course used to select the objects influencing the map, but I think the string approach in your map works just as well, or even better, since you don’t have to edit a list, but just rename the objects to be included (I’m a fan of the “endswith” variable).

So I think your version of the map is mainly only missing a texture driven “distance” value, plus the ability to make the inside or outside solid. Bonus points for separate inside and outside colors and distance (although this could maybe achieved via and output slot that’s simply and inside/outside mask - that way colors can be changed in a Mix node, using that mask).

I am still having two problems though, which I don’t know if they can be fixed:

First, the map has some alignment issues around edges. The other thing is, I can’t seem to get a completely sharp color edge, even at 128 (or more) samples for the distance and a ColorRamp set to Constant.

Just like you did above, by using a RGBMix node to transform the Fac output into a gradient, it’s possible to have some input for colors. It limits the user to have a very basic gradient (near vs far). I prefer to still output the distance value, and with that one can use a RGBMix, a ColorRamp, or even use the value for further computations.

:confused: isn’t there a socket for the “distance”??? i don’t get it.

Just checked and unfortunately there’s no attribute for the backfacing in the ‘trace’ messaging (or it has some name I don’t know). So a bit more math must be involved in this.

If you really want to have such a defined interface, than you can encapsulate the script in a python node. That way you’ll get objects lists, and have other functionality.

This is very difficult to fix. It’s inherent to the stochastic way the whole thing is sampled… If you really want perfect alignments and sharp contours, you’ll need use other methods to calculate geodesical distances on surfaces (and probably that won’t work in Cycles).

Er… of course there’s a slot for mapping distance, sorry, not sure how I missed that.

Anyway, for filling the inside (and thus the outside) the AO node works well enough (and it can be used as a mask to color the insides and outsides separately as well). Also I don’t really want the input list - as I said, using the “endswith” string is even more handy.

I don’t know that technique VRay uses for its distance texture, but it doesn’t lose alignment around edges and can be turned into a perfectly sharp edge using color correction nodes. Ah well, you’ve already done much more than I could ask for.

Hello.
I am new to blender coming from 3ds max.
I am interested in this function.
How to use this script please ?

Set your renderer to Cycles, turn ‘Open Shading Language’ on, and in your material add a Script node pointing to the script file.

2 Likes