5
\$\begingroup\$

How could I draw (stroke) the outline of a 3D object in a cartoon style with GLKit?

I'm referring to something like this:

enter image description here

However we can start with a simple line around the object and then try to improve the stroke with a shader.

I'm drawing the object with this code:

glBindVertexArrayOES(_vertexArray);
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0)

As I'm not very familial with GLKit: all the solutions I've found make use of OpenGL APIs not supported by OpenGL ES.

\$\endgroup\$
  • \$\begingroup\$ There's direct shaders for this. I'm at work and cannot link you directly, but try searching around. If it helps, the last implementations I saw revolved around the Ogre3D forums. \$\endgroup\$ – user39686 Feb 24 '14 at 13:25
  • \$\begingroup\$ That would be great! I'll have a look at the forum you mentioned, let us know if you find out any reference, thanks! \$\endgroup\$ – DAN Feb 25 '14 at 8:30
  • \$\begingroup\$ @DAN why not move your edit to its own answer? \$\endgroup\$ – Seth Battin Mar 28 '14 at 23:41
4
\$\begingroup\$

I'm not too familiar with what features aren't supported by OpenGL ES but the way I see it you have a couple options.

A quick and easy way to get a black outline effect around an object is to scale the object up slightly and render it completely black. You can then render the regular version of the model again.

Another way would be to use an edge detection post processing effect.

Also you could use N dot V to determine how much of the surface is visible. The below link has an example on that.

http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter09.html

EDIT BY DAN:

This is a solution developed thanks to the answer and comments below. As suggested I've slightly scaled the object up, rendered it completely black (only back faces) and finally rendered the regular version of the model again.

Here's the result:

enter image description here

This is the drawing method:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClear(GL_COLOR_BUFFER_BIT);

    // Set matrices
    [self setupMatrices];

    // Positions
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, cubePositions);

    // Texels
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 0, cubeTexels);

    // Normals
    glEnableVertexAttribArray(GLKVertexAttribNormal);
    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 0, cubeNormals);

    // Save the current modelview matrix in a temporary variable
    GLKMatrix4 mat = self.effect.transform.modelviewMatrix;
    self.effect.transform.modelviewMatrix = GLKMatrix4Scale(self.effect.transform.modelviewMatrix, 1.02, 1.02, 1.02);

    glDisable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    // Set the material to black
    self.effect.material.ambientColor = GLKVector4Make(0.0, 0.0, 0.0, 1.0);
    self.effect.material.diffuseColor = GLKVector4Make(0.0, 0.0, 0.0, 1.0);

    // Prepare effect
    [self.effect prepareToDraw];

    // Draw the outline
    glDrawArrays(GL_TRIANGLES, 0, cubeVertices);

    // Restore the correct material color
    self.effect.material.ambientColor = GLKVector4Make(251.0/255.0, 95.0/255.0, 95.0/255.0, 1.0);
    self.effect.material.diffuseColor = GLKVector4Make(251.0/255.0, 95.0/255.0, 95.0/255.0, 1.0);

    // Prepare effect
    [self.effect prepareToDraw];
    glEnable(GL_CULL_FACE);

    // Restore the modeview matrix
    self.effect.transform.modelviewMatrix = mat;

    // Prepare effect
    [self.effect prepareToDraw];

    // Draw the object
    glDrawArrays(GL_TRIANGLES, 0, cubeVertices);

}

Before scaling the object I save the modelview matrix in a temp variable, which I restore to draw the object in the last step. Any hint to improve the code is really appreciated!

Thanks a lot guys for your help.

Cheers, DAN

\$\endgroup\$
  • 2
    \$\begingroup\$ "scale the object up slightly and render it completely black" won't work concave shape objects or objects with holes. \$\endgroup\$ – Kromster says support Monica Feb 24 '14 at 11:57
  • \$\begingroup\$ My only object is a 3D Cube, it could work; I'm trying to write some code to do it in order to understand whether I can obtain a cartoon style with this solution.. \$\endgroup\$ – DAN Feb 24 '14 at 12:09
  • 2
    \$\begingroup\$ Rather than strictly scaling the object up (with the issues Krom Stern points out), you can push all vertices out a short distance along their normals, and render only back faces. This can generate internal lines at the edges of concavities. Special attention needs to be paid at smoothing splits though, or you'll get a gap in your lines. The example image above appears to use this technique (see the telltale sharp points at the armpits) - it's quick and dirty and good for thick lines, but not as controllable as using screenspace effects. \$\endgroup\$ – DMGregory Mar 26 '14 at 19:37
  • \$\begingroup\$ @DMGregory Actually I think it is more controllable than post-process outlines because it (the outline mesh) could be done in a 3d modelling program, tweaked by the artist (for example they could remove the artifacts at smoothing splits) then exported with the model with reverse windig of geometry. :) \$\endgroup\$ – János Turánszki Mar 28 '14 at 23:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.