Hair Interpolation in Houdini with VEX
When I started learning vex programming, one of my goals was to create a hair interpolation node in Houdini. It’s one of the most complex problems I’ve solved in the area of CG grooming. It took quite some time and a lot of trial and error. But I guess where there’s a will there’s a way. I managed to put it together in a decently optimized way. This setup interpolates existing groom curves with guide curves. Hair curves and guide curves need to be resampled to the same amount.
The principle of the setup:
Get an array of the three closest guides to each hair
get an array of distances to the closest guides and remap the values to be the sum of 1 for blending between the nearest guide’s point positions
read each closest guide’s point positions and blend between them by the distance values
set groom curves point positions to the blended point positions.
Remove all except root point:
//point wrangle int pts[] = primpoints(0,@primnum); if ( @ptnum != pts[0] ) removepoint(0,@ptnum);
Measure distance to guides:
//primitive wrangle int pts_groom[] = primpoints(0,@primnum); vector rootP = point(0,"P",pts_groom[0]); int primnum_guides[] = nearpoints(1,v@P,100,3); vector pos; float d; float dist[]; for ( int i=0; i<3; i++ ){ pos = point(1,"P",primnum_guides[i]); d = distance(rootP,pos); append(dist,d); } float x = 1/(sum(dist)); for ( int i=0; i<3; i++ ){ dist[i] *= x; } f[]@dist = dist; f@max = max(dist); i[]@primnum_guides = primnum_guides;
Remap dist falloff:
//primitive wrangle float dist[] = f[]@dist; float max = detail(0,"max"); for ( int i=0; i<len(dist); i++ ){ dist[i] = fit(dist[i],0,max,0,1); dist[i] = 1-chramp("ramp",dist[i]); } f[]@dist = dist;
Average and set point positions:
//primitive wrangle int primnum_guides[] = i[]@primnum_guides; int pts[] = primpoints(0,@primnum); vector rootP_groom = point(0,"P",pts[0]); float dist[] = f[]@dist; int guide1_pts[] = primpoints(1,primnum_guides[0]); int guide2_pts[] = primpoints(1,primnum_guides[1]); int guide3_pts[] = primpoints(1,primnum_guides[2]); vector rootP_guide_avg,avg_pos; vector pos1,pos2,pos3; vector move; int guide,guide_pts[]; for ( int i=0; i<len(pts); i++ ){ guide = primnum_guides[i]; guide_pts = primpoints(1,guide); if ( i==0 ){ pos1 = point(1,"P",guide1_pts[i]); pos2 = point(1,"P",guide2_pts[i]); pos3 = point(1,"P",guide3_pts[i]); rootP_guide_avg = ((pos1*dist[0]) + (pos2*dist[1]) + (pos3*dist[2])) / (sum(dist)); } pos1 = point(1,"P",guide1_pts[i]); pos2 = point(1,"P",guide2_pts[i]); pos3 = point(1,"P",guide3_pts[i]); avg_pos = ((pos1*dist[0]) + (pos2*dist[1]) + (pos3*dist[2])) / (sum(dist)); move = rootP_groom - rootP_guide_avg; avg_pos += move; setpointattrib(0,"P",pts[i],avg_pos); }