Using OpenGL Toon Shader in GLSL

Ben Anderson picture Ben Anderson · Apr 26, 2011 · Viewed 10.4k times · Source

I'm interested in learning how to write toon shaders in OpenGL Shading Language. I found a demo, but haven't been able to get the demo running on my computer. The trouble I'm having is with writing an application which will use this shader. Could somebody please show me how to write a simple application which would use this shader? I'm using GLSL 1.2 (OpenGL 2.1) on Linux.

Answer

George Profenza picture George Profenza · Apr 26, 2011

Here is the main sketch:

/*
* Use keys 1 - 8 to play with different GLUT Solids
* mouse affects light position
* Toon Shader by Philip Rideout:
* http://www.lighthouse3d.com/opengl/glsl/index.php?toon2
*/
import processing.opengl.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.*; 

PGraphicsOpenGL pgl;
GL gl;
GLSL toon;
GLU glu;
GLUT glut;
boolean glInit;
int glutSolidIndex = 7;

void setup()
{
  size(600, 500, OPENGL);

  glu = new GLU();
  glut = new GLUT();

  pgl = (PGraphicsOpenGL) g;
  gl = pgl.gl;
}

void draw()
{
  background(0);
  PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
  GL gl = pgl.beginGL();

  if(!glInit){
    toon=new GLSL();
    toon.loadVertexShader("toon.vs");
    toon.loadFragmentShader("toon.fs");
    toon.useShaders();

    glInit = true;
  }

  gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);

 //TRS
  gl.glTranslatef(width * .5, height * .5,0.0f);
  gl.glRotatef(160,1,0,0);
  gl.glRotatef(frameCount * .5,0,1,0);
  gl.glRotatef(frameCount * .5,0,0,1);
  gl.glScalef(80,80,80);
  // draw 
  toon.startShader();
  toon.uniform3f(toon.getUniformLocation("LightPosition"), mouseX-width*.5, -(mouseY-height*.5), 20.0f);
    gl.glColor3f(1.0f, 0.5f, 0.0f);
    glutSolid();
  toon.endShader();

  pgl.endGL();
}

void glutSolid(){
 switch(glutSolidIndex){
   case 0:
     glut.glutSolidCube(1);
   break;
   case 1:
     glut.glutSolidTetrahedron();
   break;
   case 2:
     glut.glutSolidOctahedron();
   break;
   case 3:
     glut.glutSolidDodecahedron();
   break;
   case 4:
     glut.glutSolidIcosahedron();
   break;
   case 5:
     glut.glutSolidSphere(1,16,8);
   break;
   case 6:
     glut.glutSolidTorus(.5,1,32,24);
   break;
   case 7:
     glut.glutSolidTeapot(1);
   break;
 }
}
void keyPressed(){
  if((int)key >= 49 && (int)key <= 56) glutSolidIndex = (int)(key) - 49;
}

Here is the GLSL class used:

/*
* Class posted by JohnG on the Processing forums:
* http://processing.org/discourse/yabb2/YaBB.pl?board=OpenGL;action=display;num=1159494801
* check it out for more details
*/
import processing.opengl.*;
import javax.media.opengl.*;
import java.nio.IntBuffer;
import java.nio.ByteBuffer;
import com.sun.opengl.util.BufferUtil;

class GLSL
{
  int programObject;
  GL gl;
  boolean vertexShaderEnabled;
  boolean vertexShaderSupported; 
  int vs;
  int fs;

  GLSL()
  {
    PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
    gl = pgl.gl;
    //gl=((PGraphicsGL)g).gl;
    String extensions = gl.glGetString(GL.GL_EXTENSIONS);
    vertexShaderSupported = extensions.indexOf("GL_ARB_vertex_shader") != -1;
    vertexShaderEnabled = true;    
    programObject = gl.glCreateProgramObjectARB(); 
    vs=-1;
    fs=-1;
  }

  void loadVertexShader(String file)
  {
    String shaderSource=join(loadStrings(file),"\n");
    vs = gl.glCreateShaderObjectARB(GL.GL_VERTEX_SHADER_ARB);
    gl.glShaderSourceARB(vs, 1, new String[]{
      shaderSource    }
    ,(int[]) null, 0);
    gl.glCompileShaderARB(vs);
    checkLogInfo(gl, vs);
    gl.glAttachObjectARB(programObject, vs); 
  }

  void loadFragmentShader(String file)
  {
    String shaderSource=join(loadStrings(file),"\n");
    fs = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB);
    gl.glShaderSourceARB(fs, 1, new String[]{
      shaderSource    }
    ,(int[]) null, 0);
    gl.glCompileShaderARB(fs);
    checkLogInfo(gl, fs);
    gl.glAttachObjectARB(programObject, fs); 
  }

  int getAttribLocation(String name)
  {
    return(gl.glGetAttribLocationARB(programObject,name));
  }

  int getUniformLocation(String name)
  {
    return(gl.glGetUniformLocationARB(programObject,name));
  }

  void useShaders()
  {
    gl.glLinkProgramARB(programObject);
    gl.glValidateProgramARB(programObject);
    checkLogInfo(gl, programObject);
  }

  void startShader()
  {
    gl.glUseProgramObjectARB(programObject); 
  }

  void endShader()
  {
    gl.glUseProgramObjectARB(0); 
  }

  void checkLogInfo(GL gl, int obj)  
  {
    IntBuffer iVal = BufferUtil.newIntBuffer(1);
    gl.glGetObjectParameterivARB(obj, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);

    int length = iVal.get();
    if (length <= 1)  
    {
      return;
    }
    ByteBuffer infoLog = BufferUtil.newByteBuffer(length);
    iVal.flip();
    gl.glGetInfoLogARB(obj, length, iVal, infoLog);
    byte[] infoBytes = new byte[length];
    infoLog.get(infoBytes);
    println("GLSL Validation >> " + new String(infoBytes));
  }

  void uniform3f(int location, float v0, float v1, float v2)
  {
    gl.glUniform3fARB(location, v0, v1, v2);
  }

  void uniform1i(int location, int v0)
  {
    gl.glUniform1iARB(location, v0);
  }

}

And the GLSL code, the vertex shader: toon.vs

//
// Vertex shader for cartoon-style shading
//
// Author: Philip Rideout
//
// Copyright (c) 2005-2006 3Dlabs Inc. Ltd.
//
// See 3Dlabs-License.txt for license information
//

varying vec3 Normal;

void main(void)
{
    Normal = normalize(gl_NormalMatrix * gl_Normal);
    #ifdef __GLSL_CG_DATA_TYPES // Fix clipping for Nvidia and ATI
    gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
    #endif
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

And the fragment shader: toon.fs

/* http://www.lighthouse3d.com/opengl/glsl/index.php?toon2 */

varying vec3 Normal;

uniform vec3 LightPosition;// = vec3(10.0, 10.0, 20.0);

void main()
{
    vec4 color1 = gl_FrontMaterial.diffuse;
    vec4 color2;

    float intensity = dot(normalize(LightPosition),Normal);

    if (intensity > 0.95)      color2 = vec4(1.0, 1.0, 1.0, 1.0);
    else if (intensity > 0.75) color2 = vec4(0.8, 0.8, 0.8, 1.0);
    else if (intensity > 0.50) color2 = vec4(0.6, 0.6, 0.6, 1.0);
    else if (intensity > 0.25) color2 = vec4(0.4, 0.4, 0.4, 1.0);
    else                       color2 = vec4(0.2, 0.2, 0.2, 1.0);

    gl_FragColor = color1 * color2;
}

If it helps, here is the zipped Processing project. Once you've installed Processing, unzip the file into the default Processing folder(~/Documents/Processing) and run Processing > it should show under File > Sketchbook

And here's a screenshot: GLSL toon shader example

HTH

Update

Processing now provides a nice PShader class and comprehensive tutorial. It incluses a Toon shader:

PShader toon;

void setup() {
  size(640, 360, P3D);
  noStroke();
  fill(204);
  toon = loadShader("ToonFrag.glsl", "ToonVert.glsl");
  toon.set("fraction", 1.0);
}

void draw() {
  shader(toon);
  background(0); 
  float dirY = (mouseY / float(height) - 0.5) * 2;
  float dirX = (mouseX / float(width) - 0.5) * 2;
  directionalLight(204, 204, 204, -dirX, -dirY, -1);
  translate(width/2, height/2);
  sphere(120);
}

ToonVert.glsl:

uniform mat4 transform;
uniform mat3 normalMatrix;
uniform vec3 lightNormal;

attribute vec4 vertex;
attribute vec4 color;
attribute vec3 normal;

varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;

void main() {
  gl_Position = transform * vertex;  
  vertColor = color;
  vertNormal = normalize(normalMatrix * normal);
  vertLightDir = -lightNormal;
}

ToonFrag.glsl:

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

#define PROCESSING_LIGHT_SHADER

uniform float fraction;

varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;

void main() {  
  float intensity;
  vec4 color;
  intensity = max(0.0, dot(vertLightDir, vertNormal));

  if (intensity > pow(0.95, fraction)) {
    color = vec4(vec3(1.0), 1.0);
  } else if (intensity > pow(0.5, fraction)) {
    color = vec4(vec3(0.6), 1.0);
  } else if (intensity > pow(0.25, fraction)) {
    color = vec4(vec3(0.4), 1.0);
  } else {
    color = vec4(vec3(0.2), 1.0);
  }

  gl_FragColor = color * vertColor;  
}