[HSLS] Outline render(er)

Started by
6 comments, last by galapogos22 16 years, 4 months ago
The following code is from the book 'Introduction to 3D Game Programming with DirectX 9.0' and is by Frank D. Luna and is probably copywrite in someway even though it is an example piece of code made to be used and tried. I am asking questions about it - not infringing any copywrite in any way from this (excellent) book. Don't sue me. My questions are regarding the methodology of this vertex shader. It generates an out line yes - but it has many tiny flaws. For example the outline has a different thickness depending on the current depth value. Im also not overly convinced about moving the 'edge' vertex out. Yes it will generate the quad used to render the out line but wont it mess up the triangles that use this edge vertex? Is there another way to do this - perhaps using a 'line' rendering (ie always x pixels thick) that does not involve mutilating the model you are drawing? Any input on outline methods would be appreciated.


// File: outline.txt
// Desc: Vertex shader renders silhouette edges.
//
// Globals
//
extern matrix WorldViewMatrix;
extern matrix ProjMatrix;
static vector Black = {0.0f, 0.0f, 0.0f, 0.0f};
//
// Structures
//
struct VS_INPUT
{
vector position : POSITION;
vector normal : NORMAL0;
vector faceNormal1 : NORMAL1;
vector faceNormal2 : NORMAL2;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR;
};
//
// Main
//
VS_OUTPUT Main(VS_INPUT input)
{
// zero out each member in output
VS_OUTPUT output = (VS_OUTPUT)0;
// transform position to view space
input.position = mul(input.position, WorldViewMatrix);
// Compute a vector in the direction of the vertex
// from the eye. Recall the eye is at the origin
// in view space - eye is just camera position.
Introduction to Vertex Shaders 315
Part IV
vector eyeToVertex = input.position;
// transform normals to view space. Set w
// components to zero since we're transforming vectors.
// Assume there are no scalings in the world
// matrix as well.
input.normal.w = 0.0f;
input.faceNormal1.w = 0.0f;
input.faceNormal2.w = 0.0f;
input.normal = mul(input.normal, WorldViewMatrix);
input.faceNormal1 = mul(input.faceNormal1, WorldViewMatrix);
input.faceNormal2 = mul(input.faceNormal2, WorldViewMatrix);
// compute the cosine of the angles between
// the eyeToVertex vector and the face normals.
float dot0 = dot(eyeToVertex, input.faceNormal1);
float dot1 = dot(eyeToVertex, input.faceNormal2);
// if cosines are different signs (positive/negative)
// then we are on a silhouette edge. Do the signs
// differ?
if( (dot0 * dot1) < 0.0f )
{
// yes, then this vertex is on a silhouette edge,
// offset the vertex position by some scalar in the
// direction of the vertex normal.
input.position += 0.1f * input.normal;
}
// transform to homogeneous clip space
output.position = mul(input.position, ProjMatrix);
// set outline color
output.diffuse = Black;
return output;
}


Regards galapogos
'I is what I is. Eggegegegegeg' - The wisdom of Popeye
Advertisement
The main problem with using a set number of pixels is what happens if your model is far away from the camera, will it have a huge (realtively) outline? What about on a lower resolution, do all the outlines increase? It can be a bit fiddley to think about all the little details and change it to work like this.

Another approach to getting an outline (however cheap and nasty it may seem!) is to simply render your model twice. The second time scale it up by a set amount (or create something to change ti based on another factor), flip all the normals and then render it black. Essentially rendering an inside-out model for the outline. This works pretty well considering the amount of effort required for it, but it does look a bit bad on low-poly models, etc. There are plenty of ways to expand and optimise this approach though.
I was just thinking about you said on far away 'relatively' huge outlines. Isn't that the way cartoons work? Surely every line is pencil tip thick? Though i can see how it would look odd in certain cases.

As for the render model twice method - I laughed at it then i stopped then i stroked my chin and now i am going to implement it. [grin]

Just to clarify

Render model slightly larger with normals inverted (Back face culling must be off) to black-body shape

Render it normally on top.

Hmmm wont i have to disable depth testing at some point?

Definatly interesting.... BUT the lines are not per pixel thick but still relative to depth.

Does anyone have a perpixel method i could try while i am messing about with these things?

Regards

[edit: fixed most obvious spelling mistakes)]
'I is what I is. Eggegegegegeg' - The wisdom of Popeye
What about if i rendered the mesh then on top i rendered the wireframe of the same mesh in black where only the edge's are rendered? That would give me a 1-pixel outline regardless of depth. I have no idea where to start implementing that but its an idea.

Regards
'I is what I is. Eggegegegegeg' - The wisdom of Popeye
This is the result from rendering in black wire frame with front face culling then rendering as i want on top.

I had to turn D3DRS_ANTIALIASEDLINEENABLE = TRUE on or the lines were 'bitty' as they were not thick enough.



I think it looks pretty good but the lines (even with D3DRS_ANTIALIASEDLINEENABLE = TRUE) are small. I need them thicker! Muahahaha!

Still at least it is not depth dependant now. Makes it feel more 'cartoony'.

How do i change the thickness of a line when drawing in wireFrame mode? Is it even possable?

Regards.

galapogos
'I is what I is. Eggegegegegeg' - The wisdom of Popeye
Glad to see you're having fun with it!

Try rendering the normal coloured model first, then swap the normals and scale it up, then render it again. I use different shaders when I'm doing it, and on the outline vertex shader (after usual calculations!):

normal = -normal;vertexPos *= outlineScale; 


then for the second pixel shader, just make it always return black.

The second time you render, because the mesh is inside out, you don't need to change any culling options, nor will the entire mesh be rendered (just the outline you want) because most of it is behind the already drawn main model, and so will fail depth tests.

The scaling of the model is your line thickness, where 1 or less is no line, anything bigger than 1 is a visible outline.

Here's a quick example on one of my models:
Free Image Hosting at www.ImageShack.us
PS. in the pic that should have been 1.005f and 1.015f!

Don't forget that the outline size relative to your models is really based on your world scale, etc. so it's always best to experiment.

As to your main issue, wanting a fixed size outline, you could always try editting the outlineScale based on distance from the camera for a quick fix to try and have a constant sized outline. Although I guess there are just some per pixel methods out there you could use instead! I just personally like the variable thickness =D
Ah i see! Thanks for clarifying method - i see now that i wont need to mess with culling using that method - infact its probably more efficient than the method i used to get the 1 pixel outline as that renders half the model in wireframe (though you cant see it).

I will play with ASAP!

Thanks

galapogos
'I is what I is. Eggegegegegeg' - The wisdom of Popeye
I cant get it to work!

For some reason i do not understand i cannot reverse the normals of the vertices. (see code below!)
Secondly the scaling seems stupid - see pic for example - where some bits would have outlines and some would not, if it worked at all.

The red and blue teapots have the new mthod of outlining on where (a)as i cant invert normals the outline covers most of the teapot and (b) some of it still visible (the handle and spout) where the scaling of the mesh is not uniform. Or the mesh itself is not a uniform shape.



The yellow teapot has no outline shader on it.

Below is the shader code (It is sooo simple i cannot see why it does not work!)

// Globalsextern matrix vertexTransform;extern matrix normalTransform;extern vector colour;// Structuresstruct VS_INPUT{    vector position : POSITION;    vector normal: NORMAL;};struct VS_OUTPUT{    vector position : POSITION;    vector diffuse  : COLOR;};// MainVS_OUTPUT Main(VS_INPUT input){	// zero out each member in output	VS_OUTPUT output = (VS_OUTPUT)0;	 input.position.x *= 1.05f;	 input.position.y *= 1.05f;	 input.position.z *= 1.05f;   	output.position = mul(input.position, vertexTransform);   	input.normal.w   = 0.0f;   	input.normal     = mul(input.normal,normalTransform);   	input.normal.x = -input.normal.x;   	input.normal.y = -input.normal.y;   	input.normal.z = -input.normal.z;       	// save color    	output.diffuse = colour;        	return output;}


'I is what I is. Eggegegegegeg' - The wisdom of Popeye

This topic is closed to new replies.

Advertisement