Showing posts with label distance to line segment. Show all posts
Showing posts with label distance to line segment. Show all posts

wiggles / noodles shader for OSL

The basic idea of this shader is to scatter around wiggly lines that can be used as fibers, hairs or, like in the image below, noodles.

Each line consists of a number of segments connected end to end. Each segment is angled by a random amount relative to the previous one. Segments are not straight though, but curved by a certain amount. The parameters that control the shape of the line are illustrated below, with on the right an indication of what the basic pattern looks like:

The curved segments are implemented as quadratic splines, using routines discussed in an earlier article.
If you would like to know more about programming OSL you might be interested in my book "Open Shading Language for Blender". More on the availability of this book and a sample can be found on this page.

Code and node setup

The code is pretty straightforward. Apart from a rather long list of input parameters it is mainly concerned with calculating a list of segments for each line we want to draw (there may be more than one per cell). The main trick is in line 86, where we make certain that point p1, the point that is used to control the curvature of each segment, lies on the line line through the previous control point and the end point. This ensures that each segment joins the previous one smoothly.
#include "equations.h"

#define DOT(a,b) (a[0]*b[0]+a[1]*b[1])
#define SUB(a,b) vector(a[0]-b[0],a[1]-b[1],0)

// determine if point M is inside a rectangle with a margin
int in_rectangle(point M, point a, point b,
  vector u, float W, vector v, float linewidth){
  point A=a+linewidth*(-u-v);
  point B=b+linewidth*(u-v);
  point D=B+(W+2*linewidth)*v;
  vector AM=SUB(M,A);
  vector AD=SUB(D,A);
  vector AB=SUB(B,A);
  float dotamad=DOT(AM, AD);
  float dotadad=DOT(AD, AD);
  float dotamab=DOT(AM, AB);
  float dotabab=DOT(AB, AB);
  return (dotamad > 0 && dotamad < dotadad) && 
         (dotamab > 0 && dotamab < dotabab);
}

#define CELL noise("cell", cp, seed++)
#define CELL2 vector(CELL, CELL, 0)

shader wiggles(
  point Pos=P,
  float Scale=1,

  int Number=1,

  float Length=0.5,
  float LengthVar=0,
  float Kink=0,
  float Curl=0.2,
  float Wave=30,    // degrees
  int Steps=2,
  float StepsVar=0,

  float Width=0.02,
  float WidthVar=0,

  int Seed=0,
  
  output float Fac=0
){
  point p = Pos * Scale;
  p[2]=0;
  point ip= point(floor(p[0]),floor(p[1]),0);

  int nn=1+(int)ceil(Steps*Length);

  for(int xx=-nn; xx <= nn; xx++){
    for(int yy=-nn; yy <= nn; yy++){
      int seed=Seed;
      point cp = ip + vector(xx, yy, 0);
      for(int wiggle=0; wiggle < Number; wiggle++){
        vector start = cp + CELL2;
        start[2]=0;
        vector dir = CELL2 - 0.5;
        dir[2]=0;
        dir = normalize(dir);
          
        vector perp = vector(dir[1],-dir[0],0);
        float k=0.5 + Kink * (CELL-0.5);
        float c=Curl*(CELL-0.5);
        point p1=start+k*dir+c*perp;
        for(int step=0; step < Steps; step++){
          vector ldir = dir;
          ldir *= Length + LengthVar*CELL;
          point end=start+ldir;
          if(in_rectangle(p, start, end, dir, c/2, perp, Width+WidthVar)){
            float d,t;
            if(splinedist(start, p1, end, p, d, t)){
              float localwidth = Width+WidthVar*noise("uperlin",start,t);
              if(d < localwidth){
                Fac = (localwidth - d)/localwidth;
                return;
              }
            }
          }

          if(CELL < StepsVar){
            break;
          }else{
            p1 = end + (end - p1)*(1+noise("perlin",end)*Kink); 
            start = end;
            dir = rotate(dir, radians(Wave*noise("perlin", start)), vector(0,0,0), vector(0,0,1));
          }
        }
      }
    }
  }
}  
The only other issue that needs attention is the generation of random numbers. In each cell we need a number of them and they need to be unique. We therefore add an extra seed argument to the call to noise. However, we must take care that all those numbers are generated in a repeatable way so we reset this seed for each cell to the value provided by the Seed input. This allows us to generate unique patterns for different objects sharing the same material.
The example image at the start was created with a node setup like this:

Note that the Fac output isn't simply 1 or 0 but contains the distance to the edge of the fiber and we use that to drive a bump node (through a square root math node (power 0.5) to give it a smoothly curved appearance). We use the object info node to generate a random number because the heap of noodles consists of three separate squashed half spheres. The shader expects an integer so we multiply the random value by a million to get a unique integer.
One final node of caution: this isn't a cheap shader because calculating the distance to a spline is rather expensive. OSL is quite good at optimizing expressions but still I did dpend quite some time on optimizing the splinedist() by hand. Did did indeed shave off some small percentage but the biggest win was the conversion of all calculations to two dimensions and the test to see if we are within the bound of the control rectangle before actually checking the distance to the spline (line 72 in the code)
A final thing is that most random vectors we generate don't need the z component but OSL has no notion of 2D vectors. I rewrote that in a way that doesn't waste a third of the random values calculated (the CELL2 macro). Even with these optimizations the image with the noodles took an hour to render (200 samples on a hexacore machine). That might be a bit too much but for adding realism to a sweater (in my case that means with cat hairs all over it ;-) this might be a interesting opton.

Code availability

The code is available on GitHub. For ease of use I inlined the necessary functions from equations.h so the shader can be used as is, without external dependencies.

A Hagelslag (sprinkles) OSL shader for Blender Cycles

Most of the noise patterns available in Blender are either continuous (like noise or musgrave) or serve a specific function (like voronoi or bands). None of these are easily adapted to generate the not quite microscopic dust particles that leave their marks in for example fingerprints. While developing such a shader I came across a great video by CGPGrey on Holland/The Netherlands, in which he mentions the national breakfast confectionery hagelslag (chocolate sprinkles). Because the shader we present here is easily adapted to generate sprinkle patters, I dubbed it the hagelslag shader, although originally I intended to present a picture of rod-like bacteria such as Bacillus subtilis, my guess is that people find this a tastier example.

The environment lighting and background plate in the picture is provided by HDRSource the Gold Room from HDRLabs.com. And yes I know the bread crust doesn't look tasty at all, that is why I always removed it as a child.
The code I presented earlier doesn't work on all version of the OSL compiler. The culprit was the return statement inside the actual shader definition. I think this is a bug in OSL , a return from a shader function should be possible, but I updated the code nevertheless. I tested it on Blender r53600 and r54202 on 64 bits Windows 7
The code below is self contained, which means I have included a replacement for the missing distance() function but I do not discuss that one here as I published a small blog article on it elsewhere.
The basic pattern is based on determining the distance to randomly scattered line segments. In its most pure form it might look like this:
The shader code is presented below and the inputs of the shader are (beside the position and scale) the number of sprinkles to produce per area (Np), a random Seed to vary the pattern, the Radius which controls the width of the sprinkle, and the Size which governs the length of the sprinkles. The output Fac is between 1 and 0, gradually reducing from centerline to edge.
#include "stdosl.h"

// replacement for the missing distance(p, p1, p2) function
float minimum_distance(point v, point w, point p) {
  vector s = w - v;
  float l2 = dot(s,s);
  if (l2 == 0.0) return distance(p, v);
  float t = dot(p - v, s) / l2;
  if (t < 0.0) return distance(p, v);
  else if (t > 1.0) return distance(p, w);
  vector projection = v + t * (s);
  return distance(p, projection);
}

shader sprinkles(
 point Pos = P,
 float Scale = 1,
 int Np = 1,
 int Seed = 42,
 float Radius = 0.05,
 float Size = 1,
 output float Fac = 0
){
 point p = Pos * Scale;
 point f = floor(p);
 
 int xx,yy,np;
 vector one = 1;
 
 for( xx=-1; xx<=1; xx++){
  for( yy=-1; yy<=1; yy++){
   point ff = f + vector(xx,yy,0);
   
   vector dp = vector(5,7,11);
   vector da = vector(5,3,1);
   vector dm = vector(7,5,2);
   
   for( np=0; np < Np; np++){
    vector pd1 = 2*cellnoise(ff+dp)-one;
    vector pd2 = 2*cellnoise(ff+dp+Seed)-one;
    
    dp += da;
 dp *= dm;
 dp = mod(dp,10000);
    
    point p1 = ff + pd1;
    point p2 = ff + pd2;
    
    p2 = (p2 - p1)*Size+p1;
    
    // reduce to 2D 
    p1[2]=0;
    p2[2]=0;
    p [2]=0;
    
    float r = minimum_distance(p1,p2,p);
    if ( r < Radius ) {
  //printf("%2.f %.2f\n",p,r);
     Fac = 1 - r/Radius;
    }
   }
  }
 }
}

Example node setup

The node setup for the plain pattern looks like this:
It uses a mix node that switches between two material based on whether Fac is larger than zero.
The shader node used for the image of the sandwich with the chocolate sprinkles is a variation on that:
Because the radius of a chocolate sprinkles varies a little bit along its length, we perturb the Radius with some noise (red box). The yellow box lets us switch between a completely transparent material and a chocolate material. (We use a transparent material here because we stacked two layers of sprinkles on top of each other, with different values for Seed to get a the effect of a thick layer of sprinkles. The green box converts the linear values of Fac to a rounded bump by calculating the sqaure root (power 0.5) and plugging it into a bump node. The brown box is our chocolate material, simply a dark brown but rather glossy material.

A replacement for the missing OSL distance to line segment function

The OSL implementation defines but apparently doesn't actually implement the three argument variant of the distance() function. In this short article I present a simple alternative.
While working on a new shader I noticed I got runtime errors that crashed Blender when I used the three argument variant of the distance() function to compute the shortest distance of a point to a line segment. I filed a bug report and although the Blender devs were very quick to verify and respond, the fact remains that it is a gap in the OSL libraries that won't be fixed by the Blender devs (although they have implemented a fix that will prevent Blender from crashing). That leaves us with the task of providing a workaround. Fortunately that is not too difficult.
Finding the shortest distance to a line segment is of course bssic geometry so it was easy enough to find numerous online resources. Based on those (references are in the code below) I came up with the following code:
#include "stdosl.h"

// replacement for the missing distance(p, p1, p2) function

// shortest distance to a line segment from p1 -> p2
// based on information from the following two sites:
// http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// http://paulbourke.net/geometry/pointlineplane/

float minimum_distance(point v, point w, point p) {
  // Return minimum distance between line segment vw and point p
  vector s = w - v;
  float l2 = dot(s,s);
  if (l2 == 0.0) return distance(p, v);
  float t = dot(p - v, s) / l2;
  if (t < 0.0) return distance(p, v);
  else if (t > 1.0) return distance(p, w);
  vector projection = v + t * (s);
  return distance(p, projection);
}
I will not explain it here, refer to the sources mentioned in the code if you are interested, especially Paul Bourke's site is a treasure trove of useful information.