Good stuff! So drawing the back facing elements in black, slightly enlarged, creates the thick outline we need for cartooning. It seems a bit OTT to make two passes though.
I have seen different methods, but I can't remember how they did it.
Yeah, it's a shame the gl_FrontFacing variable is only available in the fragment shader. If you could access it in the vertex shader, then you could do it all in one pass. I guess there should be a way of working out whether the face is front-facing in the vertex shader though. After the normal has been multiplied by modelMatrix, would it just be a matter of checking whether z is positive? Could it really be as simple as that? Or would you need to multiply the normal by the modelViewProjection matrix?
@Ignatz Gosh, that's pretty close. The appearance and thickness of the line varies though as the camera moves around, so that it appears and disappears at certain points. I'm going to play with it, see if I can get the line to appear steady.
What is the dot doing in your version? I get the same results when I just query the z, like this if (nView.z<=0.)
I guess that what we're seeing here, is that because the decision about what is front-facing is being decided on the vertex shader, it creates a less smooth effect as the camera rotates, as entire stretches of the line will blink in and out of visibility. I guess this is because, even though a triangular face cannot be curved (so in theory a vertex-level decision about whether a face is front-facing should be the same as a fragment-level decision), because the normals are describing a curving surface (ie they are "smooth-shading" averaged-point normals), we are not getting a consistent decision from the vertex shader about whether a face is front-facing?? Maybe this technique would work better with face normals (which would have to be on another attribute slot. Or set of attribute slots if you're key framing ....) Or gl_FrontFacing is cleverer than we thought it was.
Another issue with doing it in one pass, and I think this is again to do with the normals being averaged-point normals rather than face-normals, is the model at certain points "bleeds" into the black outline, both in terms of colour and shape. If you made the calculation with face-normals, then I expect you'd get a sharp delineation between the outline and the coloured sections.
Did a quick test, and using face-normals rather than average-point normals does get rid of the line-bleeding problem in the one-pass method, but the line still appears and disappears in chunks as the camera rotates. I also can't get a thick line with this one-pass method: if I increase the amount that the rear-facing vertices are extended by, I get a thin line, and then a gap between the line and the main body. It's as if it's only drawing the faces which are perpendicular, but is still discarding the ones facing backwards.
Yep, checking nView.z works. I was comparing nView with the camera angle, but that is not needed.
I think OpenGL is working as advertised when it discards backfacing vertices. I'm not sure there is a way to do it with one pass, especially if you are averaging normals.
Yeah, it seems the only way to get it to draw back-facing normals (needed to achieve line thickness) is to discard the front-facing ones in a 2-pass approach. Thanks for the suggestion though, it was fun trying.
In this GLSL ebook http://en.wikibooks.org/wiki/GLSL_Programming/Unity/Toon_Shading
they describe the Unity method (which I'm adapting with my 2-pass approach), but then they describe a method similar to your one (except they do this calculation on the fragment shader). The outline, in other words, is drawn out of the inside edge of the model, instead of being something which expands the model (the Unity approach). I'll investigate this later, if I have time:
just spotted a bunch of mistakes in the shader (not normalizing the normals, normalizing vPosition by mistake, not adding colour to the specular component). Edit: errors fixed, in the code above, on the blog, and on github. I also added support for having different specular intensities on different parts of the model, by storing the intensity value in the alpha of the vertex colour.
Comments
Good stuff! So drawing the back facing elements in black, slightly enlarged, creates the thick outline we need for cartooning. It seems a bit OTT to make two passes though.
I have seen different methods, but I can't remember how they did it.
Keep it up! =D>
Yeah, it's a shame the
gl_FrontFacing
variable is only available in the fragment shader. If you could access it in the vertex shader, then you could do it all in one pass. I guess there should be a way of working out whether the face is front-facing in the vertex shader though. After the normal has been multiplied bymodelMatrix
, would it just be a matter of checking whether z is positive? Could it really be as simple as that? Or would you need to multiply the normal by themodelViewProjection
matrix?You have given me an interesting problem to play with. :-?
Don't tell me the answer straight away! Give me 8 hours to work on it. I'm guessing
normal*modelViewProjection
...@yojimbo2000 - you won't get far with that, swap them round (the matrix gets multiplied on the left)
I'll post my shader solution below, don't look if you want to
@Ignatz Gosh, that's pretty close. The appearance and thickness of the line varies though as the camera moves around, so that it appears and disappears at certain points. I'm going to play with it, see if I can get the line to appear steady.
What is the
dot
doing in your version? I get the same results when I just query the z, like thisif (nView.z<=0.)
I guess that what we're seeing here, is that because the decision about what is front-facing is being decided on the vertex shader, it creates a less smooth effect as the camera rotates, as entire stretches of the line will blink in and out of visibility. I guess this is because, even though a triangular face cannot be curved (so in theory a vertex-level decision about whether a face is front-facing should be the same as a fragment-level decision), because the normals are describing a curving surface (ie they are "smooth-shading" averaged-point normals), we are not getting a consistent decision from the vertex shader about whether a face is front-facing?? Maybe this technique would work better with face normals (which would have to be on another attribute slot. Or set of attribute slots if you're key framing ....) Or
gl_FrontFacing
is cleverer than we thought it was.Another issue with doing it in one pass, and I think this is again to do with the normals being averaged-point normals rather than face-normals, is the model at certain points "bleeds" into the black outline, both in terms of colour and shape. If you made the calculation with face-normals, then I expect you'd get a sharp delineation between the outline and the coloured sections.
Did a quick test, and using face-normals rather than average-point normals does get rid of the line-bleeding problem in the one-pass method, but the line still appears and disappears in chunks as the camera rotates. I also can't get a thick line with this one-pass method: if I increase the amount that the rear-facing vertices are extended by, I get a thin line, and then a gap between the line and the main body. It's as if it's only drawing the faces which are perpendicular, but is still discarding the ones facing backwards.
Yep, checking nView.z works. I was comparing nView with the camera angle, but that is not needed.
I think OpenGL is working as advertised when it discards backfacing vertices. I'm not sure there is a way to do it with one pass, especially if you are averaging normals.
Yeah, it seems the only way to get it to draw back-facing normals (needed to achieve line thickness) is to discard the front-facing ones in a 2-pass approach. Thanks for the suggestion though, it was fun trying.
In this GLSL ebook http://en.wikibooks.org/wiki/GLSL_Programming/Unity/Toon_Shading
they describe the Unity method (which I'm adapting with my 2-pass approach), but then they describe a method similar to your one (except they do this calculation on the fragment shader). The outline, in other words, is drawn out of the inside edge of the model, instead of being something which expands the model (the Unity approach). I'll investigate this later, if I have time:
Their version works! I'll post my adaptation of it soon. It's a subtly different effect, but it looks very cool.
OK, here's the single-pass version, adapted from the GLSL e-Book. Blog here:
https://puffinturtle.wordpress.com/2015/05/28/toon-shader-redux/
And the shader:
can you provide some sample Codea code to show what the uniforms look like?
Here's the update source including the shader:
https://github.com/Utsira/examples/blob/master/Toon shader 2.lua
just spotted a bunch of mistakes in the shader (not normalizing the normals, normalizing vPosition by mistake, not adding colour to the specular component). Edit: errors fixed, in the code above, on the blog, and on github. I also added support for having different specular intensities on different parts of the model, by storing the intensity value in the alpha of the vertex colour.
@yojimbo2000 @-) (looking for a jaw-dropping smiley, but couldnt find any...)
Thanks, I'm just standing on others' shoulders!
speaking of which, this GLSL eBook is fantastic. It's aimed at Unity, but as with the code above, it's not too hard to port chunks of it to Codea:
http://en.wikibooks.org/wiki/GLSL_Programming/Unity