And there are some errors in my picture.
It was very hard to get the text to look right since it was so lengthy.
Here is the vector workup.
The first thing that must be done is find the vertexes that are silhouette edges - or vertexes that lie at the edge of the occluder object. This is a time consuming process and is far beyond the scope of this post so I will skip it for now.
Let's just say we already know the edge vertexes and they are stored in EdgeVertex[].
The variables
Our light source position is Light.Pos and is a Vector3 or D3DXVECTOR3 - 3 floats.
Known values/equations
We know where the occluder is in world space. We know where the light is in world space.
We also know the parametric equation for a line.
Code:
newPoint=Origin+(Vector*Distance);
For example if we want to extend point A at an angle of 30 degrees and a distance of 100 units in 2D:
Code:
ExtendedA.x=A.x+(cos(DEGTORAD(30))*100);
ExtendedA.y=A.y+(sin(DEGTORAD(30))*100);
To extend a 3D line from VertexA in the direction of 3D vector V for a known distance D to create VertexB we do this:
Code:
VertexB.x=VertexA.x+(V.x*D);
VertexB.y=VertexA.y+(V.y*D);
VertexB.z=VertexA.z+(V.z*D);
or in Vector notation:
Code:
VertexB=VertexA+(V*d);
This is just a simple displacement.
Back to the shadow volumes
Ok so we know the light source position and the vertex edge in question.
The vector from the occluder vertex edge to the light is:
Code:
Vector3 LightVector(EdgeVertex[i].Pos-Light.Pos);
This must be normalized so we can work with unit vectors. Otherwise we will get some very large values and it will complicate the math.
Code:
nrmLightVector=Normalize(LightVector);
This is just a simple:
Code:
...
float length=sqrt((LightVector.x*LightVector.x)+(LightVector.y*LightVector.y)+(LightVector.z*LightVector.z));
float oneOverLength=1.0f/length;
nrmLightVector3.x=LightVector.x*oneOverLength;
nrmLightVector3.y=LightVector.y*oneOverLength;
nrmLightVector3.z=LightVector.z*oneOverLength;
...
Ok so now we have a normalized light vector called nrmLightVector. Let's say we want to extend our shadow for our object for 100 units. In actuality you would either compute the angle of the light source in relation to the actual height/width of the object. After all shadow lengths are proportional to the width/height of an object and the angle of the incoming light rays. But we are just going to use 100. Our shadow is going to be 100 units long.
Our new shadow vertex is:
Code:
ShadowVertex=EdgeVertex[i]+(nrmLightVector*100.0f);
We do this for both sides of the object and for all corner edge vertexes. We don't want to do this for every edge vertex because every edge does not contribute directly to the shadow. For instance in a very tall building that has uniform width all the way to the top. To compute a shadow volume for this we would simply use the top two vertexes that formed the roof and were facing AWAY from the light source. They must be facing away from the light source because if we selected those facing it, the shadow would actually pierce our object. Not what we want.
Again this is not totally correct because of our 'fake' shadow distance but it is close enough for simplicity's sake.
So now we have created two new vertexes from two edge vertexes on our object. The only thing we do now is cap them with a line at the far end and near end (draw a horizontal line between the two new vertexes and draw a line between the two known vertexes). These act as our shadow volume 'caps'. Note that I'm only using two vertexes to explain this, but in actual practice you would probably have more. Now triangulate the shadow volume. You are done with the shadow volume.
After this comes the important part of figuring out what lies in shadow and what does not based on depth-fail or depth-pass methods so we can paint the stencil buffer correctly. After we figure this out we then render the shadow volume into the stencil buffer. Pixels that fail the stencil buffer test get 'darkened'. How dark is up to you. Pixels that pass the test are simply rendered as is.
There is a lot more to the technique - far more than I can explain here. But that is the general idea of it.