Writing a basic OSL color shader

The following is an introduction to basic OSL shader syntax using a simple color blending shader example. a more general explanation of the subject can be read here.

Notes:
> It’s highly recommended to get acquainted with basic C language syntax, since it’s the basis for common shading languages like OSL, HLSL and GLSL.
> More detailed information about writing OSL shaders can be found in the osl-languagespec PDF document from ImageWorks’s OSL GitHub.

This example shader blends 2 color sources according to the surface viewing angle (aka “facing ratio” or “incident angle” or “Perpendicular-Parallel”). the user can choose a facing (“front”) color or texture, a side color or texture, and the shader’s output bell be a mix of the these 2 inputs that depends on the angle the surface is viewed at.

This is the full code of the shader, you’re also welcome to download the it here.

 #include "stdosl.h"
 
shader cglColorAngleBlend
[[ string help = "Blend colors by view angle" ]]
(
	color facing_color = color(0, 0, 0)
		[[ string help = "The color (or texture) that will appear at perpendicular view angle" ]],
	color side_color = color(1, 1, 1)
		[[ string help = "The color (or texture) that will appear at grazing view angle" ]],
	float base_blend = 0.0
		[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle",
		float min = 0.0, float max = 1.0]],
	float curve_exponent = 1.0
		[[ string help = "A power exponent value by which the blend value is raised to control the blend curve",
		float min = 0.001, float max = 10.0]],
	output color color_out = color(1, 1, 1))
{
	// calculate the linear facing ratio:
	float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
	// calculate the curve facing ratio:
	float final_blend_ratio = pow(facing_ratio , curve_exponent);
	// blend the facing color:
	color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
	// blend and output the final color:
	color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}

The first statment:

#include "stdosl.h"

The #include statement is a standard C compiler directive to link the OSL source code library code file stdosl.h to the shader’s code, so that the OSL data types and functions in the code will be recognized.
* Some systems compile the code successfully without this statement. I’m not sure if their compiler links stdosl.h automatically or not.

The double-square bracketed statements provide both help annotations and value range limits for the shader parameters:

[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle", float min = 0.0, float max = 1.0]]

Note that these statements are appended to single parameters in the shader right before the comma character that ends the parameter statement.
* Not all shading systems that supprt OSL also implement the annotations in the shader’s interface generated by the host software (the shader will work, but it’s parameters wont be described and limited to the defined value range).

Removing the #include statement, annotations and comments,
We can see that the OSL shader structure is very similar to a C function:

shader <identifier> (input/output parameters) {code}

shader cglColorAngleBlend
(
color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1)
)
{
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}

First the data type, in this case shader, followed by the shader identifier, in this case “cglColorAngleBlend”:

shader cglColorAngleBlend

After the shader’s type and identifier, a list of parameters is defined within parentheses, separated by comma’s. these parameters define the shader’s input’s, outputs, and default values. Output parameters are preceded by the output modifier.

(
<parameter type> <parameter identifier> = <parameter default value>,
<parameter type> <parameter identifier> = <parameter default value>,
output <parameter type> <parameter identifier> = <parameter default value>
)

(
color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1)
)

In this case the shader has 4 user input parameters, and 1 output parameter.
2 color type parameters, “facing_color” and “side_color” for the facing and side color that will be blended together, a float* parameter “base_blend” that specifies how much of the side color will be mixed with the facing color regardless of view angle, and a second float parameter “curve_exponent” specifying a power exponent by which the blend value will be raised to create a non-linear blend curve.
The output parameter “color_out” is a color that will calculated by the shader.
* Note that even though the the output parameter will be calculated by the shader, it is required to define a default value for it for the shader to compile.

After the shader parameters, enclosed within curly braces is the actual body of the shader code, containing the instructions, each ending by a semicolon ; character:

{
<instruction>;
<instruction>;
…..
}

{
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));
}

I the case of our shader the first code instruction is to define a new float internal variable named “facing_ratio”, calculate the surface/view angle and assign the resulting value to it:

float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;

The expression:

acos( abs( dot( -I, N ) ) )  / M_PI_2

calculates incident angle, i.e. angle between 2 vectors, originating at the surface shading point, one pointing towards the origin of the incoming ray, and the other the surface normal, as a factor of 0 to 1 representing 0 – 90 degrees.
These 2 vector are easily obtained through the built in OSL global variables N and I. N is the surface normal at the shading point, and I is the incoming ray vector pointing to the shading point which is inverted in this case to point backwards by  typing a minus before it: -I.
The incident angle is calculated in radians as the arc-cosine of the dot-product of N and -I and then divided by half a π to convert it to a linear factor of 0 to 1 representing 0 to 90 degrees in radians, M_PI_2 being a convenient half π constant.
* M_PI being a full π, M_2PI being 2π representing 180 degrees in radians and 360 degrees respectively (OSL provides there are more constants in this series).

The second instruction raises the facing ratio that was calculated in the previous instruction by a power value provided by the curve_exponent input parameter, to create a non linear angle/color blend in values other than 1.0.
The resulting modified blend value is stored in a new internal variable final_blend_ratio:

float final_blend_ratio = pow(facing_ratio , curve_exponent);

Note:
We could avoid setting a new variable by modifying the value of  the facing_ratio variable, and we could also combine the these 2 instruction into 1 bigger expression like this:

pow( acos( abs( dot( -I, N ) ) ) / M_PI_2 , curve_exponent )

But I decided to keep it separated into 2 variable and 2 instructions for clarity.
* try modifying the code as an exercise

The third instruction modifies the input color facing_color by premixing it with the input side_color according to the percent give by the input parameter base_blend and assigns the resulting color to a new internal variable named final_facing_color:

color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);

The expression:

( facing_color * ( 1 – base_blend ) ) + ( side_color * base_blend )

Calculates a linear combination** (linear interpolation) between the 2 input colors using the base_blend as a 0 – 1 factor between them.
* Note that OSL allows to define arithmatic operations freely between colors and floats.

The forth and final instruction creates the final mix between the modified facing color stored in final_facing_color variable and the side color given by the input color parameter side_color, by again, calculating a linear combination between the 2 colors, this time using final_blend_ratio variable value we calculated previously as the combination factor, and very importantly, finally, assigning the mixed result to the shader output parameter color_out so it will be the final output of the shader:

color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));

This screen capture shows this shader at work in Blender and Cycles, connected to a Principled BSDF shader as it’s base color source:
Annotation 2020-06-17 235933

Thats it! 🙂
Hope you find this article informative and useful.

Clarifications:

* A “float” data type is simply the the computer-science geeky way of saying “accurate non-integer number”. when we have to store numbers that can describe geometry and color, we need a data type that isn’t limited to integers so for that purpose we use float values. there’s actually a lot more to the float formal definition in computer science, but for our purpose here this will suffice.

** A Linear Combination, or Linear interpolation (lerp) is one of the most useful numerical operations in 3D geometry and color processing (vector math):
A * ( 1 – t ) +  B * t 
A and B being your source and target locations or colors or any other value you need to interpolate and t being the blending factor from 0 – 1.

Related posts:

  1. OSL read-list
  2. What are OSL shaders?
  3. Using OSL Shaders in Maya and Arnold
  4. Using OSL Shaders in Blender and Cycles
  5. Using OSL Shaders in 3ds max and V-Ray

3ds max – Using a GradientRamp procedural texture in Mapped mode

Software:
3ds max 2020

Using the GradientRamp procedural texture map in Mapped mode can very useful for creating procedural material effects.
The Idea is that the lightness value from a different map will determine what part of the GradientRamp is sampled.

In this example the GradientRamp uses values produced by a procedural Falloff map set to Perpendicular-Parallel mode, as its coordinates source, to create richly colored metal that changes its Hue depending on View/Incident angle:

Annotation 2020-05-02 184426

In this example the GradientRamp uses values produced by a procedural Noise map as its coordinates source to create an irregular color effect:

Annotation 2020-05-02 184849

Note:
The examples here were rendered using V-Ray Next for 3ds max, but this technique could also be used with other rendering engines.

 

Related:

  1. 3ds max Island/seashore tip

UE4 – Basic Material Blending

Software:
Unreal Engine 4.24

The example explained in this article is creating a blend between a mud material, and a mud-leaves material using a mask (Alpha) texture.
>>
The scanned PBR materials in demonstrated in this post are from Texture Haven (texturehaven.com)

How does it work?
There is actually no blending of Unreal materials, but rather a regular Unreal material in which each of the parameters is defined as a linear blend between 2 different source values for that parameter.
We could create such a material blueprint that uses a Lerp (Linear Interpolate) node’s to provide each of the material parameters with a blend of 2 input textures/colors or parameters, connecting the alpha texture to all the Lerp nodes’s Alpha input, and effectively achieve blending of 2 different materials, but it would be a complex blueprint in which it’s very inconvenient to design each of the individual materials participating in the blend:
Annotation 2019-12-25 232734

This complexity can be greatly simplified by collecting each of the participating materials parameters into a Material Attributes data structure.
The Material Attributes data structure contains all the data needed to compile a material, and allows input, output, and processing of this data as a single blueprint data stream (connection).
For example, when the material parameters are grouped as a Material Attributes data structure, they can be blended by connecting them into a single BlendMaterialAttributes node, instead of “Lerping” (blending) between 2 inputs to create each individual material parameter, which produces an unworkable complex material blueprint like the previous example.

> To collect material parameters into a Material Attributes data structure, connect them into a MakeMaterialAttributes node:
annotation-2019-12-26-015532.jpg

> To create a blend between 2 Material Attributes data streams, use the BlendMaterialAttributes node:
* The Alpha parameter determines the weights of the blend (a black and white texture can be connected to it as the blend mask)
Annotation 2019-12-26 015717

> In order for the material output to receive a grouped Material attributes input instead of individual inputs for each parameter, select the material output, and in the Details panel, check the Use Material Attributes option:
matatts

Using the Material Attributes data structure, the blended material’s Blueprint in now much simpler and cleaner, while producing the exact same result as before:
Annotation 2019-12-26 021435

But designing 2 different materials within one material Blueprint is still far from being ideal..
What if we want to use just one of these materials on some surfaces?
What if the individual materials are not as simple as the materials shown here, it would be mush more efficient to be able to have one Blueprint for each of the materials allowing to focus on its development and preview it.
We can achieve this desired workflow by developing each of the materials as a Material Function.
Each of the participating materials is created as a Material Function with a Material Attributes output.

> One of the huge advantages of UE4’s material editing is that it allows us to preview a full material while developing it as a Material Function.
* This may sound trivial, but it isn’t. the Material Function isn’t compiled by itself as a material, it just produces data needed to define a material. in many other media production systems, this would have meant that you can develop data within the function but only preview it in the main material where the function is used.

> Learn how to create Material Functions

The Material Function defining the mud material:
Annotation 2019-12-26 024834.jpg

The Material Function defining the mud-leaves material:
Annotation 2019-12-26 024906.jpg

The Blend material using the Material Function nodes:
Annotation 2019-12-26 025111

Note:
When blending a non-metallic material with a metal material, the alpha values (mask colors) should be only 0 or 1 (black or white), otherwise blend areas that have a mid-range metallic value will make no sense visually.
> A RemapValueRange node can be used to force a color threshold on the mask texture or value.

Related:
Material Functions
Material Instances
Texture Painting

UE4 – Material Functions explanation and Example

Software:
Unreal Engine 4.24

Material Functions encapsulate shading flow graphs (material blueprints) into reusable shading nodes that have their own inputs and outputs.
This allows development of custom shading nodes, and saving the time it takes to recreate the same flow graphs multiple times or even copy and paste material flow graphs.
Common shading processes and operations that have to be performed in many different materials, and even multiple times in a single material can be defined as a Material Function for quick and easy re-usability.
Material functions can also be used to encapsulate a full material blueprint with a Material Attributes output. this provides a convenient workflow for blending different materials together.

In this post I’ll detail the steps needed to create and use a Material Function.
The Material Function example we’ll create, called “ColorAngleBlend” performs a commonly needed shading operation of blending 2 colors or textures according to the surface viewing angle (facing ratio).

The ColorAngleBlend Material Function will have the following inputs:

  1. color a:
    The color or texture appearing when viewing the surface at perpendicular angle.
  2. color b:
    The color or texture appearing when viewing the surface at grazing view angle.
  3. curve exponent:
    The steepness of the blend curve between the colors, 1 being a linear blend and higher values displaying color a at more angles “pushing” color b to be seen only at grazing angle.
  4. base color blend:
    The percent of color b seen at perpendicular view angle.
  5. normal:
    bump normals input.

The final “ColorAngleBlend” Material Function Blueprint:
* The internals of the “ColorAngleBlend” Material Function
Annotation 2019-12-24 151655.jpg

An example of the “ColorAngleBlend” Material Function node used to create a reach view-angle dependent color blend for a steampunk metal material:
Annotation 2019-12-24 152625

An example of the “ColorAngleBlend” Material Function node used to create a reach color for a car-paint material:
Annotation 2019-12-24 152036

An example of the “ColorAngleBlend” Material Function node used to create a washed-out effect for a cloth material:Annotation 2019-12-24 151927
Steps for creating the “ColorAngleBlend” Material Function:

  1. In the content browser, create a Material Function Object and name it “ColorAngleBlend”:
    Annotation 2019-12-24 161420
    Annotation 2019-12-24 162132
  2. Double click the ColorAngleBlend Material Function to open it for editing:
    Annotation 2019-12-24 162209
  3. Click the background of the work space to deselect the Output Result Node,
    So that the Details panel on the left will display the Material Functions‘s properties.
    Type a description into the Description field, check the Expose to Library option so that the new Material Function will be available to all materials in the Palate and node search, and define in which node categories it should appear:
    Annotation 2019-12-24 162805
  4. Select the Output Result node and in the Details panel on the left set its output name to “color”:
    Annotation 2019-12-24 163948
  5. Add a Linear Interpolate (Lerp) node, a Fresnel node and a Transform Vector (Tangent space to World space) node to the Blueprint and connect the nodes like this:
    * The Lerp node will blend the 2 color inputs with the Fresnel providing view angle data as the alpha for the Lerp.
    The Transform Vector  node is needed to convert normal (bump) input to world space for the Fresnel node.
    Annotation 2019-12-24 164646.jpg
  6. Adding function inputs:
    Create 2 Function Input nodes, in their Details panel, name them “color a” and “color b”, leave their Input Type as default Vector3D, check the option Use Preview Value as Default, number their Sort Priority parameters 0 and 1 to make them appear as the first inputs of the ColorAngleBlend node as it will appear when used in a material, and connect them to the Lerp node’s A and B inputs:
    Annotation 2019-12-24 165934
  7. Adding function inputs:
    Create 2 new Function Input nodes, name them “curve exponent” and “base color blend”, this time set their Input Type to Scalar, check the option Use Preview Value as Default, set their Sort Priority parameters to 2 and 3 and connect their outputs to the Fresnel node’s ExpoentIn and BaseReflectFractionIn inputs:
    Annotation 2019-12-24 171212.jpg
  8. Adding function inputs:
    Create the final Function Input node, name it “normal“, set its Input Type to Vector3D, check its Preview Value as Default option, set its Sort Priority to 4, and connect its output to the Fresnel node’s Normal input:
    Annotation 2019-12-24 171640
  9. Adding default inputs:
    Finally, add constant nodes to serve as default input values for the Material Function.
    A pure white Constant3Vector (color) constant as the default value for “color a” input,
    A pure black Constant3Vector (color) constant as the default value for “color a” input,
    A Constant with value 1.0 as the default value for “curve exponent” input,
    A Constant with value 0.0 as the default value for”base color blend” input,
    A pure blue Constant3Vector (color) constant as the default value for “normal” input.
    > Tip for quick creation of constant value nodes
    Annotation 2019-12-24 172438
  10. Save the new Material Function.

To use the new ColorAngleBlend Material Function create a new material, in the node search start typing color… to locate the ColorAngleBlend node and create it, and connect it to the desired material input.

Annotation 2019-12-24 173747

Annotation 2019-12-24 173809.jpg

> Material Functions can also be used by dragging them from the Content Browser into the Material Blueprint.

Related posts:
UE4 – Procedural Bump Normals
UE4 – Material Instances
UE4 – Fresnel node
UE4 – Triplanar mapping

Basic Cloth Material in Arnold for Maya

Software:
Maya 2018 | Arnold 5

An example of a basic traditional (not scanned) cloth material setup in Arnold 5 for Maya using an aiStandardSurface shader.

Untitled-1.jpg

The shading network uses a classic angle dependent color blend to simulate the color of the cloth being washed out at grazing angle of view.

Explanation of the node graph:

Untitled-2.jpg

  1. A black and white fabric weave texture that will serve as input for multiple shading channels.
    * This is actually not the best example of such a pattern, and could be replaced with a much better texture.
    cotton grey bump
  2. A remapValue node is used to set contrast to the fabric pattern (reduce contrast in this case) prior to it being multiplied with the fabric colors.
    * Note that only one of the textures RGB channels is connected to the remapValue node since it’s a float (mono) processor and not RGB.
    Untitled-3
    * Note that depending on the fabric texture, you may have to design different curves to achieve the right effect.
  3. Two colors are defined with colorConstant nodes:
    A deep color as the main fabric color, and a washed out color for grazing angle view (“side color”).
  4. An aiFacingRatio node is used as an input for incident angle info.
    * Note that in this case I checked the node’s invert option to make it behave more like other systems I’m used to (if you don’t use invert, the angle blend curve in 5 will be different..)
  5.  A remapValue node used to set the angle blend curve or in other words, how much does the color appears washed out per change of view angle of the cloth surface.
    * The longer it take the curve to become steep from left to right, the more the main color will be dominant before the washed out color will appear.
    Untitled-4
  6. A colorCorrect node is used in this example just as a way to convert the remapped float value back to RGB for being multiplied with the cloth colors.
    * We could also connect it directly to the individual float components of the RGB colors but this way the node graph is cleaner.
  7. A multiplyDivide node is used to multiply the processed fabric texture with the 2 fabric colors “baking” the pattern into the color.
  8. A blendColors node is used to blend the 2 processed fabric colors together according to the processed facingRatio angle input.
    The result is the final cloth color that is connected to the aiStandardSurface shader.
  9. An aiBump2d node is used to convert the fabric pattern to normal data that will be connected to the aiStandardSurface shader to produce bumps.
  10. An aiStandartSurface shader serving as the main shading node for this material.
    * Note that under Geometry the Thin Walled option is checked so that the Subsurface layer of the shader will act as a Paper Shader rather than SSS.
    * The main cloth color is connected to the SubSurface Color input.
    Untitled-5

 

More Arnold shading posts

UE4 – Material Fresnel Node

Software:
Unreal Engine 4.18

The UE4 Fresnel node is actually a “Facing Ratio” node (aka Perpendicular / Parallel) with some extra control.
It basically allows controlling material effects according to the incident angle the surface is viewed at, which is a hugely important feature for designing advanced material effects.

Untitled-3

Exponent:
The steepness of the value / angle curve.

Base Reflect Fraction:
The value at perpendicular angle.

Normal:
An option to connect World Space surface normals input to affect the output of the Fresnel node.
* Tangent Space normals must be converted to World Space by using a Transform Vector node.

Note:
A value of 1.0 for the Exponent parameter, and a value of 0.0 for the Base Reflect Fraction will produce a linear “Facing Ratio” (“Perpendicular / Parallel”) falloff blend.

Examples of different values:
Untitled-3
Untitled-4

More info:
https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/HowTo/Fresnel/

Related posts:

  1. UE4 – basic architectural glazing material
  2. Understanding Fresnel reflections