← All work OSL shader

Succulent Plant Commercial

A short product spot whose hero object is a wood and resin vase. Rather than texture it, I wrote the surface procedurally in Open Shading Language inside Houdini, matching a photographic reference.

Role
Shader author, look development
Tool
Houdini, Open Shading Language
Context
SCAD, VSFX 319
Year
2024
Breakdown

1 problems, 1 answers.

Problem 01

Write an OSL shader for the vase that matches the reference photo.

Approach

Wood grain function. Perlin noise fed into a sine wave gives the grain; a second noise adds random patches and dots so no two tiles read the same.

Resin function. A recursive noise builds the fluid, translucent resin, with controls for scale, frequency and intensity.

Burn masking. Subtle burn marks darken the wood near the resin for an aged, weathered edge.

Controls. Grain scale, resin properties and color blending are all exposed, so the shader stays art-directable rather than a one-off.

Reference A, the vase the shader was matched againstClick to enlarge
Source

The shader, as written.

Open Shading Language, authored in Houdini. Wood grain, resin and burn masks are separate functions so each can be tuned on its own.

//Making a function for the wood grain 
float WoodPattern(float scale2, float scale1, float frequency, int Variation, float patchamount)
{
    //Normalizing (putting object in object space)
    vector objP = transform("object",P); 
    
    //Making vector for the noise used, and a vector for the scaling of the noise
    vector NoiseWood = objP;
    vector NoiseWoodVec = vector(1,10,1);
    
    //Vector options for wood rotation
    
    vector p1 = vector (0,0,0);
    vector p2 = vector (0,0,1);
    vector p3 = vector (1,0,0);
    
    //Rotating the Wood pattern 
    vector objProtate = rotate(objP,7.75,p1,p2);
    
    //Multiplying noise by the scale
    NoiseWood = noise("perlin",objProtate*NoiseWoodVec*scale1);
 
    //Using differnet noises from the vector as tiles need an alternating pattern, so I should be able to call 2 different noise floats from the function
    //To get wood pattern using a noise into a sine wave to get the wood like grain look
    
    float stripeWood= 1-((0.5*sin(frequency*NoiseWood[Variation]))+0.5);
    
    //Making noise for adding random value patches for the grain to give the grain a more natural look
    float Woodpatchrandom = smoothstep(0.1,0.7,noise (objP*14));
    
    //Making noise for adding random dots/marks across the pattern for a more natural look
    float Woodpatchdots = smoothstep(0.3,1,noise ("perlin",objP*patchamount));
    
    //Adding random values across the pattern
    stripeWood = stripeWood * Woodpatchrandom;
    //Adding random dots across the pattern 
    stripeWood = stripeWood + Woodpatchdots;
return stripeWood;
}
//Make function for resin
float resinweight(float ResinScale, float ResinFrequency ,float ValueAdd, float ValueMultiply)
{
    vector Vector = P;
    vector p = Vector * ResinScale;
    int MaxIterations = 64;
    for(int i=1; i < MaxIterations; i++)
    {
        vector newp = p;
        newp[0]+=ResinFrequency/float(i)*sin(float(i)*p[1]+35/12.4+0.5*float(i))+22.9;
        newp[1]+=ResinFrequency/float(i)*sin(float(i)*p[0]+35/12.4+0.5*float(i+8.93))-22.9;
        p=newp;
    }
   float ColorF = ValueMultiply*(smoothstep(0.2,1,sin(35+p[1]+p[0]-M_PI/(4.0+sin(35)))*0.3+0.5)+ValueAdd);
   return ColorF;
}
shader vase(
//inputs
//Base color inputs 
color WoodColor = color(0.7,0.41,0.13),
color GrainColor = color(0.88,0.54,0.2),
color ResinColor = color(0.27,0.46,0.17),
//Wood Grain control inputs
float Wood_Grain_Scale =0.5[[float min= 0, float max=10]],
float Wood_Grain_Stretch =0.7[[float min= 0, float max=10]],
float MaskScale = 1[[float min= 0, float max=100]],
float rotatewoodMask = 1[[float min= 0, float max=100]],
float frequencyWood =16[[float min= 0, float max=100]],
float ScaleWoodMask =16[[float min= 0, float max=100]],
float debug1 =0.3[[float min= -1, float max=1]],
float debug2 =1[[float min= 0, float max=1]],
float debug3 =1[[float min= 0, float max=10]],
//vector uvw = P,
output color Base_Color = 0,
output color Roughness = 0,
output color Transmitioncolor = 0,
output float TransmitionMask =0,
)
{
vector objP = transform("object",P); 
//Rotation Function
vector p1 = vector (0,0,0);
vector p2 = vector (0,0,1);
vector p3 = vector (0,1,0);
vector p4 = vector (1,0,0);
float rotates = 22.5;
float rotatewood = 37.5;
vector objProtate = rotate(objP,rotates,p1,p3);
vector objProtateWood = rotate(objP,rotatewoodMask,p1,p2);
float WoodPattern1 = WoodPattern(Wood_Grain_Stretch*3, Wood_Grain_Scale*5, frequencyWood, 0, 62);
float WoodPattern2 = WoodPattern(Wood_Grain_Stretch*3, Wood_Grain_Scale*5-0.5, frequencyWood, 0, 62);
//making a vector to subtract one wood color from to get a very slightly darker one
vector WoodColorSubtract = vector (0.1,0.05,0.03);
color BaseColorMix1 = mix (WoodColor,GrainColor,WoodPattern1);
color BaseColorMix2 =mix (WoodColor-WoodColorSubtract,GrainColor-WoodColorSubtract,WoodPattern2);
float WoodGrainMask1= smoothstep(0.3,0.5,noise(objProtateWood*ScaleWoodMask));
color BaseColorMixcombo = mix(BaseColorMix1,BaseColorMix2,WoodGrainMask1);
//Make mask for resin 
vector noisescaleresin = vector (1,0.9,1);
//Masking in woodpatterns with different colors together
//Resin/WoodSeperationMask
float VaseMask2 = step(0.33,sin(5.45*noise (objProtate*MaskScale*noisescaleresin)));
//Roughness Mask
float VaseMask5 =0.9* (1-step(0.33,sin(5.45*noise (objProtate*MaskScale*noisescaleresin))));
//using smoothstep and pushing values further than 1 and -1 to get more of a gradient for the desired look
//Burn on Wood Mask
float VaseMask3 = smoothstep(-0.55,0.35,sin(5.45*noise (objProtate*MaskScale*noisescaleresin)));
//Burn on Resin Mask
float VaseMask4 = smoothstep(debug1,debug2,sin(5.45*noise (objProtate*MaskScale*noisescaleresin)));
color BaseColorMixcomboedge= mix(BaseColorMixcombo,BaseColorMixcombo/1.35,(VaseMask3*3));
color BaseColorMask = mix (BaseColorMixcomboedge,1,VaseMask2);
//Dividing Color to get the warm burn look
color ResinColorBurn = color(-0.05,0.55,0.96);
color ResinColorMask = mix (ResinColor/(debug3*ResinColorBurn),ResinColor,VaseMask4);
Base_Color = BaseColorMask;
Roughness = VaseMask5;
Transmitioncolor=ResinColorMask;
TransmitionMask= VaseMask2;
}
Contact

Let's build something you can stand inside.

Available for XR development and technical art work. Email is the fastest way to reach me.