Halloween Costume ideas 2015

OSL / Blending Mode Functions

Simple Blend Dissolve using OSL


It has been a while since last post. I have a lot to think in my mind. Basically, on one side I like to start making a more finished/polished artworks, while on the other side, I always love the idea of exploring and experimenting with creative ideas in Computer Graphics in general and especially in Blender.

I think I can do both (Quality vs Creative Quantity). Just takes a bit more time in refining.

I also want to go back to doing Animation, although at the same time, I love doing technical works: Rigging, VFX, Particles, etc.

The thing I found with Shader Writing is that this is a never-ending study and exploration. Well almost like everything in CG is like that. The more we dig, the more we found a new complexity. I like the idea of understanding the simplicity of things. Complexity is not always needed.

Anyway, for this post on OSL, I think I will just dump one big large code that is basically containing some useful functions in relation to BLENDING MODE. We probably learn about Layer Blending from Photoshop, as we know we have few Blending Modes to mix 2 layers of pixels together:
  • Multiply
  • Darken
  • Lighten
  • Dodge
  • Burn
  • etc
Most of information and code about Blending is pretty much translated from book "The Renderman Shading Language Guide" by Rudy Cortes and Saty Raghavachary.
http://www.amazon.com/The-RenderMan-Shading-Language-Guide/dp/1598632868

DESIGN PHILOSOPHY OF SHADER WRITING
I like to quote a paragraph from the book above from page 362-363 in regards to Procedural Pattern Creation and Design:
"The most effective strategy to employ toward procedural pattern generation is to divide and conquer. When presented with a pattern to synthesize, the idea is to break the pattern into distinct layers, create each layer using an independent block of code, and then combine the layers to obtain the pattern sought."

I have my own frustration when learning this Shader Writing. Sometimes it feels like re-inventing the wheels. Also, there are not many experts out there care to share their knowledge to help newbies. Lots of important documents are old and starting to break as we are moving to a more modern way. There is as if a huge gaps that artists to leap ahead in order for them to be able to take advantage of the tools.

I think we can take the analogy of Instant Ramen and real Ramen/Noodles made by hand. Or any kind of instant food (pizza, pasta, fried rice etc). Handmade takes longer but may taste a lot better and fresh and healthier too. Understanding of ingredients will make you a better chef. These days, people living in modern city got their fruits, vegetables and meats already pre-made and so easy to cook. Of course it is not always necessary to be able to cut the meat or to plant vegetables and wait for fruits, because that will not be efficient, however, a certain kind of understanding of the component will help us a lot when we are using "all purpose nodes" to create custom original texture that we needs.

More reading as recommendation:
http://www.renderman.org/RMR/Publications/

I really do learn a lot about Shader Writing from RSL (Renderman Shading Language). Slowly, hopefully, we all can gain enough knowledge to translate RSL knowledge to OSL. Somebody with good Shader Writing and OSL knowledge and experience ought to write a NEW book to help future shader writers in near future!

Now, that book above also mentioned a few times that the origin of the blending mode functions were taken from: http://www.pegtop.net/delphi/articles/blendmodes/

The "pegtop" website is the work of Jens Gruschel. So I have to thank him also for the knowledge. These are priceless bits of knowledge of the legacy of shader writing.

NOTE: In this code example, I am also using OSL syntax that load Image File from directory.

I am using these 2 images as Texture to use inside OSL:
TEAPOT A: This is a generic Blender Cycles Render of a basic teapot I modeled in Blender a while ago.
TEAPOT B: This is a photograph of this awesome limited edition Renderman walking teapot (thanks Moxy!), 
Using the code below, we are loading the 2 image textures above and blend the two together.

It is very simple thing that we can easily do using Blender Mix Node, but with the idea of learning OSL Shader Writing, we are trying to recreate it with code.

The always handy MIX node.

THE "BLEND MODE" CODE

// HELPER FUNCTIONS THAT CHANGES THE BLENDING
// SOURCE:
//    The RenderMan Shading Language Guide Book
//    By Rudy Cortes and Saty Raghavachary
//    Also: Works of Jens Gruschel (http://pegtop.net)

// OVER MODE
color colorOver(color BaseMap, color Layer, float LayerOpac){
    return mix(BaseMap, Layer, LayerOpac);
}

// ADD MODE
color colorAdd(color BaseMap, color Layer, float LayerOpac){
    return BaseMap + (Layer * LayerOpac);
}

// SUBSTRACT MODE
color colorSubstract(color BaseMap, color Layer, float LayerOpac){
    return BaseMap + ((Layer-1) * LayerOpac);
}


// MULTIPLY MODE
color colorMultiply(color BaseMap, color Layer, float LayerOpac){
    return BaseMap * ((Layer * LayerOpac) + (1-LayerOpac));
}

// COLOR DISSOLVE
color colorDissolve(color BaseMap, color Layer, float LayerOpac){
    //float Random = 5.0;
    //getattribute("object:random", Random);
    float myRandom = noise("snoise", P * 7);
    color out;
    if(myRandom < (LayerOpac))
        out = Layer;
    else
        out = BaseMap;
        
    return out;
}

// SCREEN MODE
color colorScreen(color BaseMap, color Layer, float LayerOpac){
    return 1 - ((1-BaseMap) * (1-Layer * LayerOpac));
}

// COLOR OVERLAY
color colorOverlay(color BaseMap, color Layer, float LayerOpac){
    //float layerval = colorToFloat(Layer);
    //float layerval = Layer[1];
    
    // color to grayscale (luminocity method)
    // READ: http://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/
    float layerval = 0.21 * Layer[0] + 0.71 * Layer[1] + 0.07 * Layer[2];

    return (layerval>0.5) ? (2*BaseMap*Layer*LayerOpac)+BaseMap*(1-LayerOpac):
    1 - ((1-BaseMap) * (1-Layer*LayerOpac))*(2-(1-LayerOpac));
}

// COLOR DARKEN
color colorDarken(color BaseMap, color Layer, float LayerOpac){
    float baseval = 0.21 * BaseMap[0] + 0.71 * BaseMap[1] + 0.07 * BaseMap[2];
    float layerval = 0.21 * Layer[0] + 0.71 * Layer[1] + 0.07 * Layer[2];
    return (baseval<layerval) ? BaseMap: Layer * LayerOpac + (BaseMap*(1-LayerOpac));
    
}

// COLOR LIGHTEN
color colorLighten(color BaseMap, color Layer, float LayerOpac){
    float baseval = 0.21 * BaseMap[0] + 0.71 * BaseMap[1] + 0.07 * BaseMap[2];
    float layerval = 0.21 * Layer[0] + 0.71 * Layer[1] + 0.07 * Layer[2];
    return (baseval>layerval) ? BaseMap: Layer * LayerOpac + (BaseMap*(1-LayerOpac));
    
}

// COLOR DIFFERENCE
color colorAbs(color col){
    return color(abs(col[0]), abs(col[1]), abs(col[2]));
}

color colorDifference(color BaseMap, color Layer, float LayerOpac){
    return colorAbs(BaseMap - (Layer * LayerOpac));
}

// COLOR HARDLIGHT
color colorHardlight(color BaseMap, color Layer, float LayerOpac){
    float layerval = 0.21 * Layer[0] + 0.71 * Layer[1] + 0.07 * Layer[2];
    return (layerval<0.5) ? (2*BaseMap*Layer*LayerOpac)+BaseMap*(1-LayerOpac):1-((1-BaseMap)*(1-Layer*LayerOpac))*(2-(1-LayerOpac));
    
}


// COLOR SOFTLIGHT
color colorSoftlight(color BaseMap, color Layer, float LayerOpac){
    color ctemp = BaseMap * Layer;
    return mix(BaseMap, ctemp+(BaseMap*(1-((1-BaseMap)*(1-Layer))-ctemp)), LayerOpac);
}

// COLOR DODGE
color colorDodge(color BaseMap, color Layer, float LayerOpac){
    color ctemp = mix(BaseMap, BaseMap/max(1-Layer, color(0.00001)), LayerOpac);
    return clamp(ctemp, color(0), color(1));
}

// COLOR BURN
color colorBurn(color BaseMap, color Layer, float LayerOpac){
    color ctemp = mix(BaseMap, 1-((1-BaseMap)/max(Layer, color(0.00001))), LayerOpac);
    return clamp(ctemp, color(0), color(1));
}



shader blending (
    color BaseMap = color(1,0,0),
    color Layer = color (0,1,0),
    float LayerOpac = 0.5,
    string filenameA = "/Users/jimmygunawan/Desktop/teapotA.png" ,
    string filenameB = "/Users/jimmygunawan/Desktop/teapotB.png" ,
    vector Pos = P,
    output color ColOut = color(0.2)
)
{

    color TextureA = texture(filenameA, Pos[0], 1.0 - Pos[1]);
    color TextureB = texture(filenameB, Pos[0], 1.0 - Pos[1]);
    
    // change the BLENDING MODE using the helper function of your choice above
    //ColOut = colorOver(TextureA, TextureB, LayerOpac);
    //ColOut = colorBurn(TextureA, TextureB, LayerOpac);
    //ColOut = colorHardlight(TextureA, TextureB, LayerOpac);
    ColOut = colorDissolve(TextureA, TextureB, LayerOpac);
}


Feel free to modify and mix the code above.



QUESTION ARISE...

If we compare the built-in Blender MIX Node that does blending with the function above, we can see that the result is a bit different sometimes. I am wondering why is that happening? Is it because things got "normalized" with the premade node and more correct?

I guess I need to compare the code from the book with what comes with Blender. Further investigation needed.

However, with the above code example, we start to think about LAYERING in OSL. OSL is certainly handy for:
- Procedural Pattern and Texture creation
- Texture Mapping
- Complex Blending
- Utility

I guess, the "best" of OSL is when shader writing is used together with the node network, so this is something we (more artists than coders) can always kept in mind.


REWRITING OSL CODE FOR LAYERING

Still reading this book, but I am most interested with Chapter 12 "Procedural Patterns".

Although some part is still quite complicated to understand, the book is quite consistent in term of explaining the concept of "LAYERING" and "PROCEDURAL PATTERN".

This one example is also translated from the book to OSL. It seems complex at a glance, but this is actually simple and it explains a lot of things. I like how the code is nicely organized and every functions are separated from the main Shader code:

  • Helper Function to Rotate Texture in 2D: rotate2d() 
  • Few other helper functions: pulse(), repeat(), blend()
  • Concept of Layering and Opacity (Masking) using code.

CODE:

// converted from book Renderman Shading Language Guide
// page 370, for shader called "Optical Illussion"
// OSL code translation by Jimmy Gunawan

// the following HELPER FUNCTIONS was translated from 'rmannotes.sl'

float pulse (
    float a, 
    float b, 
    float fuzz, 
    float x
)
{
    return (smoothstep((a)-(fuzz), (a), (x)) - smoothstep((b)-(fuzz), (b), (x)));
}

float repeat(
    float x,
    float freq
)
{
    return (mod((x) * (freq), 1.0));
}

/*
rotate2d is taken from rmannotes.sl
2D rotation of point(x,y) about origin(ox,oy) by and angle rad.
the resulting point is (rx, ry).

*/

void rotate2d (
    float x,
    float y,
    float rad,
    float ox,
    float oy,
    float rx,
    float ry
)
{
    rx = ((x) - (ox)) * cos(rad) - ((y) - (oy)) * sin(rad) + (ox);
    ry = ((x) - (ox)) * sin(rad) + ((y) - (oy)) * cos(rad) + (oy);
}


color blend(
    color a,
    color b,
    color x
)

{
    return ((a) * (1-(x)) + (b) * (x));
}

    
shader optill(
    float fuzziness = 0.025,
    float freq = 10,
    float thickness1 = 0.75,
    float thickness2 = 0.75,
    float rot = 45,
    color baseColor = color(0,0,0),
    color c1 = color(1,0,0),
    color c2 = color(0,1,0),
    vector Pos = P,
    output color ColOut = color(1)
)
{
    color surface_color;
    color layer_color;
    
    color surface_opac;
    color layer_opac;

    float fuzz = fuzziness;
    float ss;
    float tt;
    
    surface_color = baseColor;
    surface_opac = color(0);
    
    // assign pixel position as s and t
    float s = Pos[0];
    float t = Pos[1];
    
    // rotate stuff in 2D
    rotate2d(s,t,radians(rot), 0.5, 0.5, ss, tt);    
    
    // repeat pattern
    ss = repeat(ss, freq);
    tt = repeat(tt, freq);
    
    float pwh = thickness1 * 1.0/freq;
    float pwv = thickness2 * 1.0/freq;
    
    // This is Layer 1 covering Layer 0 (Base Layer)
    layer_opac = pulse(0.5-pwh, 0.5+pwh, fuzz, ss);
    surface_color = blend(surface_color, c1, layer_opac);

    // This is Layer 2 covering Layer 1
    layer_opac = pulse(0.5-pwv, 0.5+pwv, fuzz, tt);
    surface_color = blend(surface_color, c2, layer_opac);

    ColOut = surface_color;
    
    // I omited some code in relation to "Opacity"
    // because I am not quite sure how it works.
    // However I will revise it in the future, hopefully!
    
}

The result of the above code can be seen as below.

We have 3 Layers (Color):
- Base
- c1
- c2




We can then try something like below:



We are using Image Textures and using the code above to layer them together:





Thing started to get more interesting, right? I thought so too.

Some parts may still be abstract, but when the code is arranged this way, we started to see something that looks like typical 2D Layerings in 2D image editing packages.

All contained within a single node, instead of complex noodles of node network.

The book is really teaching concept of:
- MULTI LAYER
- OPACITY or MASK
- BLEND as CODE

When thinking of Shader this way, whether we are using pre-made nodes, custom procedural textures, or image textures, or combinations of everything, thing is a lot more simple. We separate the problem in layers and then we will start LAYERING the COMPLEXITY, while still having enough CONTROL to achieve the look we like.

EXPLICIT vs IMPLICIT FUNCTIONS

At this moment, perhaps for me, the hardest part of the code is to think of Pattern Creation "IMPLICITELY", instead of "EXPLICITELY".

When doing coding to draw a shape in program like Processing, we can simply say things like:
ellipse(5,5,0,0); 
// draw an ellipse shape of size of 5x5 pixel at position (0,0).
// http://processing.org/reference/ellipse_.html

That's above is actually a simple function that is using EXPLICIT FUNCTIONS.

However, when doing SHADER WRITING (like OSL, OpenGL, RSL), even to create a simple shape of ellipse, we need to think kind of inside out. We are using IMPLICIT FUNCTIONS.
http://www.sciencehq.com/mathematics/implicit-and-explicit-functions.html


MORE OSL RELATED LINKS

http://spectralbattle.wordpress.com/2011/04/12/added-new-osl-function-texture/
http://blenderthings.blogspot.com.au/2013/01/gabor-noise-for-cycles-osl.html
http://blendbits.blogspot.com.au/2013/05/lens-flare-shader-for-blender-cycles.html
http://blenderartist.org/forum/showthread.php?293132-PYLA-PhYsically-correct-LAyers-with-OSL
http://peter.cassetta.info/material-library/submitting-help/
http://www.elysiun.com/forum/showthread.php?306353-Starscape-Texture-OSL
http://www.blenderartists.org/forum/showthread.php?277986-Random-Color-OSL
http://www.sfdm.scad.edu/faculty/mkesson/vsfx419/wip/spring11/eric_kurzmack/toon.html
http://blenderthings.blogspot.com.au/2013/02/simplex-noise-for-open-shading-language.html



Post a Comment

MKRdezign

Contact Form

Name

Email *

Message *

Powered by Blogger.
Javascript DisablePlease Enable Javascript To See All Widget