Help end child hunger

Toon Shading – Version I

 
Prev: Toon Shading Next: Toon - Version II
 

The first version presented in here computes an intensity per vertex. Then the fragment shader uses the vertex interpolated intensity to compute a tone for the fragment. The vertex shader must therefore declare a varying variable to store the intensity. The fragment shader must declare the same variable, also using the varying qualifier, to receive the properly interpolated value for the intensity.

The light direction could be defined in the vertex shader as a local variable or as a constant, however having it as a uniform variable provides more freedom since it can be set arbitrarily on the OpenGL application. The light’s direction variable will be defined in the shader as

	uniform vec3 lightDir;

For now, lets assume that the light’s direction is defined in world space.

The vertex shader has access to the normals, as specified in the OpenGL application, through the attribute variable gl_Normal. This is the normal as defined in the OpenGL application with the glNormal function, hence in model local space.

If no rotations or scales are performed on the model in the OpenGL application, then the normal defined in world space, provided to the vertex shader as gl_Normal, coincides with the normal defined in the local space. The normal is a direction and therefore it is not affected by translations.

Because both the normal and the light’s direction are specified in the same space, the vertex shader can jump directly to the cosine computation between the light’s direction, i.e. lightDir, and the normal, i.e. gl_Normal. The cosine can be computed using the following formula

	cos(lightDir,normal) = lightDir . normal / ( |lightDir| * |normal| )

where “.” is the inner product, aka as the dot product. This can be simplified if both the gl_Normal and lightDir are normalized, i.e.

	| normal | = 1
	| lightDir | = 1

Hence if these two conditions are guaranteed the computation for the cosine can be simplified to

	cos(lightDir,normal) = lightDir . normal

Since the variable lightDir is supplied by the OpenGL application we can assume that it arrives at the shader already normalized. It would be a waste of time having to normalize it for every vertex, instead of performing it only when the light direction changes. Also it is reasonable to expect that the normals from the OpenGL application are normalized.

Therefore the cosine, which we will store in a variable named intensity, can be computed with the dot function provided by GLSL.

	intensity = dot(lightDir, gl_Normal);

The only thing that’s left to do in the vertex shader is to transform the vertex coordinates. The complete code for the shader is as follows:

uniform vec3 lightDir;
varying float intensity;

void main()
{
	intensity = dot(lightDir,gl_Normal);
	gl_Position = ftransform();
}

If you want to use the OpenGL variable for the lights position, gl_LightSource[0].position, instead of the uniform lightDir then you could use the following code:

varying float intensity;

void main()
{
	vec3 lightDir = normalize(vec3(gl_LightSource[0].position));
	intensity = dot(lightDir,gl_Normal);

	gl_Position = ftransform();
}

Now, in the fragment shader, all that’s left to do is to define a color for the fragment based on the intensity. The intensity must be passed on to the fragment shader, since it is the fragment shader that is responsible for setting the colors for fragments. As mentioned before, the intensity will be defined as a varying variable on both shaders, hence it must be written in the vertex shader for the fragment shader to read it.

The color can be computed in the fragment shader as follows:

	vec4 color;

	if (intensity > 0.95)
		color = vec4(1.0,0.5,0.5,1.0);
	else if (intensity > 0.5)
		color = vec4(0.6,0.3,0.3,1.0);
	else if (intensity > 0.25)
		color = vec4(0.4,0.2,0.2,1.0);
	else
		color = vec4(0.2,0.1,0.1,1.0);

As can be seen from the code above, the brightest color is used when the cosine is larger than 0.95 and the darker color is used for cosines smaller than 0.25.

All there is left to do in the fragment shader is to set the gl_FragColor based on the color. The code for the fragment shader is:

varying float intensity;

void main()
{
	vec4 color;
	if (intensity > 0.95)

		color = vec4(1.0,0.5,0.5,1.0);
	else if (intensity > 0.5)
		color = vec4(0.6,0.3,0.3,1.0);
	else if (intensity > 0.25)
		color = vec4(0.4,0.2,0.2,1.0);
	else
		color = vec4(0.2,0.1,0.1,1.0);
	gl_FragColor = color;

}

The following image shows the end result, and it doesn’t look very nice does it? The main problem is that we’re interpolating the intensity. This is not the same as computing the intensity with the proper normal for the fragment. Go on to the next section to see toon shading done properly!

 

Prev: Toon Shading Next: Toon - Version II
 

  One Response to “Toon Shading – Version I”

  1. So… I’m a noob at lighting… and I cannot get the colors to change based on the intensity. Which is always the lowest value for some reason.

    I try doing the following to enable a lighting scenario, but something is a miss.

    //Enable and specify some lighting.
    gl.glEnable(GL2.GL_LIGHTING);
    gl.glEnable(GL2.GL_LIGHT0);
    float[] ambient = {1, 0, 0};
    float[] pos = {1, 1, 1};
    gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambient, 0);
    gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, pos, 0);

    //put some geometry and pass normals too it.
    gl.glColor3f(1, 0, 0);
    gl.glBegin(GL2.GL_TRIANGLES);
    gl.glNormal3f(0, 0, 1);
    gl.glVertex3d(-1, 0, -5);
    gl.glNormal3f(0, 0, 1);
    gl.glVertex3d(1, 0, -5);
    gl.glNormal3f(0, 0, 1);
    gl.glVertex3d(0, 1, -5);
    gl.glEnd();

    My shaders basically consist of everything in the tutorial. Some assistance would be greatly appreciated. Thanks!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: