-=[Blendo]=-


--=[ Index | Materials | GLSL ]=--

Toon Shader

blend0.jpg
suzzie.jpg

This tutorial will cover assigning two materials to a single model and applying a shader to each material in one shot.
Pick up your favorite blender model to work with, Suzanne will work if you don't have one. I will be using the blendo model to work with.

The blendo model consists of two parts. The eye and the body. We want to assign two different materials to the parts. The easiest way to do this is separate the eye form the body, and add a new material to each part, then join them back together.

You should see something like this on the material.
mat006.jpg

With the basic materials set up on the mesh we can move on to writing the vertex and fragment programs. Since the shader is dependent on lights, it might be wise to add a one to the scene now.

Start with the script:

##-----------------------------------------------------------------------------
## Blendo Start
##-----------------------------------------------------------------------------
import GameLogic
ObjectList = GameLogic.getCurrentScene().getObjectList()

# -------------------------------------
ShaderObjects = [ObjectList['OBCube']]
MaterialIndexList = [0]
# -------------------------------------


# -------------------------------------
VertexShader = """
void main() {
        gl_Position = ftransform();
}
"""

FragmentShader = """

void main() {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
"""

def MainLoop ():
        # for each object supplied
        for obj in ShaderObjects:

                mesh_index = 0
                mesh = obj.getMesh(mesh_index)

                while mesh != None:
                        
                        # for each material in the mesh
                        for mat in mesh.materials:
                                
                                # sanity check.. 
                                # is the use blender materials option checked?
                                if not hasattr(mat, "getMaterialIndex"):
                                        return
                                
                                # 0->15 avaiable
                                mat_index = mat.getMaterialIndex()

                                # find an index from the list                           
                                found = 0
                                for i in range(len(MaterialIndexList)):
                                        if mat_index == MaterialIndexList[i]:
                                                found=1
                                                break
                                if not found: continue

                                shader = mat.getShader()
                                if shader != None:
                                                
                                        if not shader.isValid():

                                                shader.setSource(VertexShader, FragmentShader,1)

                        mesh_index += 1
                        mesh = obj.getMesh(mesh_index)


## call it
MainLoop()

The vertx shader is very simple. We need three variables to pass to the fragment program:
1). The normal vector on the vertex.
2). The vector from the light position to the vertex.
3). The vertex color.

varying vec3 normal_vector;
varying vec3 light_vector;
varying vec4 vertex_color;

void main()
{
        vec3 vert =(gl_ModelViewMatrix * gl_Vertex).xyz;
        light_vector = gl_LightSource[0].position.xyz - vert;
        vertex_color = gl_Color;
        
        normal_vector = gl_NormalMatrix *gl_Normal;

        gl_Position = ftransform();
}

Now for the fragment shader. Copy the varying variables from the vertex shader to the fragment shader. We need to normalize the light vector and the normal vector. A color variable is also needed that we can modify.
Some temporary variables will do the trick.

vec3 l = normalize(light_vec);
vec3 n = normalize(normal_vec);
vec4 color = material_color;

Calculate the dot product between the light vector and the normal and clamp it in the range of 0,1.

ndotl = clamp(dot(n,l), 0.0, 1.0);

Now create some cells based on the dot product value. The lower the value is, multiply the material color by a smaller value. The first cell should be brighter so we will add instead of multiply. Then apply the color to the pixel

varying vec3 normal_vector;
varying vec3 light_vector;
varying vec4 vertex_color;

void main(){
        vec3 l = normalize(light_vector);
        vec3 n = normalize(normal_vector);
        float ndotl = clamp(dot(n,l), 0.0, 1.0);
        
        vec4 color = vertex_color;
        
        if(ndotl >0.9)
                color += 0.1;
        else if(ndotl > 0.8)
                color *= 0.8;
        else if(ndotl > 0.5)
                color *= 0.5;
        else if(ndotl >0.4)
                color *= 0.4;
        else if(ndotl >0.2)
                color *= 0.2;
        else 
                color *=0.0;

        gl_FragColor = color;
}

Add the vertex and fragment programs to the template. Also add the model name to the shader objects list. The blendo model has two materials assigned to it, so add 0,1 for the material indices.
Final script:

##-----------------------------------------------------------------------------
import GameLogic
ObjectList = GameLogic.getCurrentScene().getObjectList()

# -------------------------------------
ShaderObjects = [ObjectList['OBBlendo']]
MaterialIndexList = [0,1]
# -------------------------------------


# -------------------------------------
VertexShader = """
varying vec3 normal_vector;
varying vec3 light_vector;
varying vec4 vertex_color;

void main()
{
        vec3 vert =(gl_ModelViewMatrix * gl_Vertex).xyz;
        light_vector = gl_LightSource[0].position.xyz - vert;
        vertex_color = gl_Color;
        normal_vector = gl_NormalMatrix *gl_Normal;
        gl_Position = ftransform();
        
}

"""

FragmentShader = """
varying vec3 normal_vector;
varying vec3 light_vector;
varying vec4 vertex_color;

void main(){
        vec3 l = normalize(light_vector);
        vec3 n = normalize(normal_vector);
        float ndotl = clamp(dot(n,l), 0.0, 1.0);
        
        vec4 color = vertex_color;
        
        if(ndotl >0.9)
                color += 0.1;
        else if(ndotl > 0.8)
                color *= 0.8;
        else if(ndotl > 0.5)
                color *= 0.5;
        else if(ndotl >0.4)
                color *= 0.4;
        else if(ndotl >0.2)
                color *= 0.2;
        else 
                color *=0.0;

        gl_FragColor = color;
}
"""

def MainLoop ():
        # for each object supplied
        for obj in ShaderObjects:

                mesh_index = 0
                mesh = obj.getMesh(mesh_index)

                while mesh != None:
                        
                        # for each material in the mesh
                        for mat in mesh.materials:
                                
                                # sanity check.. 
                                # is the use blender materials option checked?
                                if not hasattr(mat, "getMaterialIndex"):
                                        return
                                
                                # 0->15 avaiable
                                mat_index = mat.getMaterialIndex()

                                # find an index from the list                           
                                found = 0
                                for i in range(len(MaterialIndexList)):
                                        if mat_index == MaterialIndexList[i]:
                                                found=1
                                                break
                                if not found: continue

                                shader = mat.getShader()
                                if shader != None:
                                                
                                        if not shader.isValid():

                                                shader.setSource(VertexShader, FragmentShader,1)

                        mesh_index += 1
                        mesh = obj.getMesh(mesh_index)


## call it
MainLoop()



--[- snailrose -]--