Stencil Buffer (Shadow) Problem

Started by
11 comments, last by Christian Johansson 18 years, 5 months ago
Okey i got Shadows up and set, renders fine. Now to my prob. Let's say i have a PointLight @ 6.0,6.0,6.0 and a Crate @ 0.0,0.0,0.0 (Center) it casts a shadow of the crate on the floor. BUT when i move the Crate in let's say + X-Axis the Crates moves but the light still casts a shadow as if the Crate was @ 0.0,0.0,0.0. I know it's my Shadow Part that is the prob, however i've tried another Shadow Example that moves the shadow with the Crate BUT the light casts the shadow as if the light was moved WITH the crate even if it's still @ same place. So i guess i will stick wih my shadow example i'm using now and trying to get some help instead. Here's my code:

void renderShadowToStencilBuffer( void )
{
    // Disable z-buffer writes (note: z-testing still occurs), and enable the
    // stencil-buffer
    Device->SetRenderState( D3DRS_ZWRITEENABLE,  FALSE );
    Device->SetRenderState( D3DRS_STENCILENABLE, TRUE );

    // Dont bother with interpolating color
    Device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT );

    // Set up stencil compare fuction, reference value, and masks.
    // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true.
    // Note: since we set up the stencil-test to always pass, the STENCILFAIL
    // renderstate is really not needed.
    Device->SetRenderState( D3DRS_STENCILFUNC,  D3DCMP_ALWAYS );
    Device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
    Device->SetRenderState( D3DRS_STENCILFAIL,  D3DSTENCILOP_KEEP );

    // If z-test passes, inc/decrement stencil buffer value
    Device->SetRenderState( D3DRS_STENCILREF,       0x1 );
    Device->SetRenderState( D3DRS_STENCILMASK,      0xffffffff );
    Device->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff );
    Device->SetRenderState( D3DRS_STENCILPASS,      D3DSTENCILOP_INCR );

    // Make sure that no pixels get drawn to the frame buffer
    Device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    Device->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_ZERO );
    Device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );

	if( g_bTwoSidedStencilsAvailable == true )
    {
        // With 2-sided stencil, we can avoid rendering twice:
        Device->SetRenderState( D3DRS_TWOSIDEDSTENCILMODE, TRUE );
        Device->SetRenderState( D3DRS_CCW_STENCILFUNC,  D3DCMP_ALWAYS );
        Device->SetRenderState( D3DRS_CCW_STENCILZFAIL, D3DSTENCILOP_KEEP );
        Device->SetRenderState( D3DRS_CCW_STENCILFAIL,  D3DSTENCILOP_KEEP );
        Device->SetRenderState( D3DRS_CCW_STENCILPASS, D3DSTENCILOP_DECR );

        Device->SetRenderState( D3DRS_CULLMODE,  D3DCULL_NONE );

        // Draw both sides of shadow volume in stencil/z only
        Device->SetTransform( D3DTS_WORLD, &m_matCrate );
        m_pShadowVolume->render( Device );

        Device->SetRenderState( D3DRS_TWOSIDEDSTENCILMODE, FALSE );
    }
    else
    {
        // Draw front-side of shadow volume in stencil/z only
        Device->SetTransform( D3DTS_WORLD, &m_matCrate );
        m_pShadowVolume->render( Device );

        // Now reverse cull order so back sides of shadow volume are written.
       Device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

        // Decrement stencil buffer value
        Device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECR );

        // Draw back-side of shadow volume in stencil/z only
        Device->SetTransform( D3DTS_WORLD, &m_matCrate );
        m_pShadowVolume->render( Device );
    }

    // Restore render states
    Device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD );
    Device->SetRenderState( D3DRS_CULLMODE,  D3DCULL_CCW );
    Device->SetRenderState( D3DRS_ZWRITEENABLE,     TRUE );
    Device->SetRenderState( D3DRS_STENCILENABLE,    FALSE );
    Device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
}

void renderShadowToScene( void )
{
    // Set render states
    Device->SetRenderState( D3DRS_ZENABLE,          FALSE );
    Device->SetRenderState( D3DRS_STENCILENABLE,    TRUE );
    Device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    Device->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );
    Device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

    Device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    Device->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
    Device->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );
    Device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    Device->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
    Device->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );

    // Only write where stencil value >= 1 (count indicates # of shadows that
    // overlap that pixel)
    Device->SetRenderState( D3DRS_STENCILREF,  0x1 );
    Device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_LESSEQUAL );
    Device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP );

    // Draw a big, gray square
    Device->SetFVF( ShadowVertex::FVF_Flags );
    Device->SetStreamSource( 0, m_pBigSquareVB, 0, sizeof(ShadowVertex) );
    Device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

    // Restore render states
    Device->SetRenderState( D3DRS_ZENABLE,          TRUE );
    Device->SetRenderState( D3DRS_STENCILENABLE,    FALSE );
    Device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
}
[/SOURCE]
And here's the code for create it inside "Main loop":

D3DXMatrixIdentity( &m_matCrate );
			D3DXVECTOR3 vLightInWorldSpace(plight1.Position.x, plight1.Position.y, plight1.Position.z);
			D3DXVECTOR3 vLightInObjectSpace;
			D3DXMATRIXA16 matInverse;
			D3DXMatrixInverse( &matInverse, NULL, &m_matCrate );
			D3DXVec3TransformNormal( &vLightInObjectSpace, &vLightInWorldSpace, &matInverse );
			m_pShadowVolume->buildShadowVolume( Crate, vLightInObjectSpace );
[/SOURCE]
The plight1 is the pointlight named plight1. Best regards, Christian.
Q: How does a UNIX Guru do sex? A: unzip; strip; touch; finger; mount; fschk; more; yes; umount; sleep " -- Martin Kahlert
Advertisement
The problem sounds like a confusion of the co-ordinate systems in use. I saw that the shadow volume is computed on each run of the rendering loop (what actually could be optimized), so I assume that the shadow volume computation itself is the problem.

First, if the moved crate does not pull the shadow with it, but left the shadow as if the crate still be located at (0,0,0), I assume that the vertices of the model of the crate are not transformed into the same space later used during rendering.

E.g. if rendering is done in global space, and the crate's location (and orientation, and possibly also scale) is given in global space, then the vertices have to be transformed by the frame defining the crate in the world.

Furthurmore, I saw that the light's location in object space (I assume the space of the create is meant) is overhanded into the shadow volume calculation. That lets me assume that the resulting shadow volume geometry will be given in object space of the crate. So the question is, what space is active when the rendering is done?
Hi,

if m_pShadowVolume->buildShadowVolume(Crate, vLightInObjectSpace); is the line that build the shadow volume, then the error probably comes from this method. So post the code and we'll be able the spot it ^^
For your problem, you have to transform the vertices used for the shadow extrusion by the model matrix.

And for the problem with the "another Shadow Example" ... I don't understand it ^^ Try posting a screenshot ^^
Quote:Original post by paic
Hi,

if m_pShadowVolume->buildShadowVolume(Crate, vLightInObjectSpace); is the line that build the shadow volume, then the error probably comes from this method. So post the code and we'll be able the spot it ^^
For your problem, you have to transform the vertices used for the shadow extrusion by the model matrix.

And for the problem with the "another Shadow Example" ... I don't understand it ^^ Try posting a screenshot ^^


Oops... yeah there's more code... sorry...

Here it comes: (shadowvolume.h):
class ShadowVolume{public:    void    reset() { m_dwNumVertices = 0L; }    HRESULT buildShadowVolume( LPD3DXMESH pObject, D3DXVECTOR3 vLight );    HRESULT render( LPDIRECT3DDEVICE9 pd3dDevice );private:	void addEdge( WORD* pEdges, DWORD& dwNumEdges, WORD v0, WORD v1 );	D3DXVECTOR3 m_pVertices[32000]; // Vertex data for rendering shadow volume    DWORD       m_dwNumVertices;};struct ShadowVertex{	D3DXVECTOR4 p;	 //D3DXVECTOR4 n;    D3DCOLOR    color;	enum FVF	{		FVF_Flags = D3DFVF_XYZRHW | D3DFVF_DIFFUSE	};};//-----------------------------------------------------------------------------// Name: render// Desc://-----------------------------------------------------------------------------HRESULT ShadowVolume::render( LPDIRECT3DDEVICE9 pd3dDevice ){    pd3dDevice->SetFVF( D3DFVF_XYZ );    return pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLELIST, m_dwNumVertices/3,                                        m_pVertices, sizeof(D3DXVECTOR3) );}//-----------------------------------------------------------------------------// Name: buildShadowVolume()// Desc: Takes a mesh as input, and uses it to build a shadow volume. The//       technique used considers each triangle of the mesh, and adds it's//       edges to a temporary list. The edge list is maintained, such that//       only silohuette edges are kept. Finally, the silohuette edges are//       extruded to make the shadow volume vertex list.//-----------------------------------------------------------------------------HRESULT ShadowVolume::buildShadowVolume( LPD3DXMESH pMesh, D3DXVECTOR3 vLight ){    // Note: the MESHVERTEX format depends on the FVF of the mesh    struct MESHVERTEX { D3DXVECTOR3 p, n; FLOAT tu, tv; };    MESHVERTEX* pVertices;    WORD*       pIndices;    // Lock the geometry buffers    pMesh->LockVertexBuffer( 0L, (LPVOID*)&pVertices );    pMesh->LockIndexBuffer( 0L, (LPVOID*)&pIndices );    DWORD dwNumFaces    = pMesh->GetNumFaces();    // Allocate a temporary edge list    WORD* pEdges = new WORD[dwNumFaces*6];    if( pEdges == NULL )    {        pMesh->UnlockVertexBuffer();        pMesh->UnlockIndexBuffer();        return E_OUTOFMEMORY;    }    DWORD dwNumEdges = 0;    // For each face    for( DWORD i=0; i<dwNumFaces; i++ )    {        WORD wFace0 = pIndices[ 3 * i + 0 ];        WORD wFace1 = pIndices[ 3 * i + 1 ];        WORD wFace2 = pIndices[ 3 * i + 2 ];        D3DXVECTOR3 v0 = pVertices[ wFace0 ].p;        D3DXVECTOR3 v1 = pVertices[ wFace1 ].p;        D3DXVECTOR3 v2 = pVertices[ wFace2 ].p;        // Transform vertices or transform light?        D3DXVECTOR3 vCross1(v2-v1);        D3DXVECTOR3 vCross2(v1-v0);        D3DXVECTOR3 vNormal;        D3DXVec3Cross( &vNormal, &vCross1, &vCross2 );        if( D3DXVec3Dot( &vNormal, &vLight ) >= 0.0f )        {            addEdge( pEdges, dwNumEdges, wFace0, wFace1 );            addEdge( pEdges, dwNumEdges, wFace1, wFace2 );            addEdge( pEdges, dwNumEdges, wFace2, wFace0 );        }    }    for( i=0; i<dwNumEdges; i++ )    {		        D3DXVECTOR3 v1 = pVertices[pEdges[2*i+0]].p;        D3DXVECTOR3 v2 = pVertices[pEdges[2*i+1]].p;		D3DXVECTOR3 v3 = v1 - vLight*500;		D3DXVECTOR3 v4 = v2 - vLight*500;        // Add a quad (two triangles) to the vertex list        m_pVertices[m_dwNumVertices++] = v1;        m_pVertices[m_dwNumVertices++] = v2;        m_pVertices[m_dwNumVertices++] = v3;        m_pVertices[m_dwNumVertices++] = v2;        m_pVertices[m_dwNumVertices++] = v4;        m_pVertices[m_dwNumVertices++] = v3;    }    // Delete the temporary edge list    delete[] pEdges;    // Unlock the geometry buffers    pMesh->UnlockVertexBuffer();    pMesh->UnlockIndexBuffer();    return S_OK;}//-----------------------------------------------------------------------------// Name: addEdge()// Desc: Adds an edge to a list of silohuette edges of a shadow volume.//-----------------------------------------------------------------------------void ShadowVolume::addEdge( WORD* pEdges, DWORD& dwNumEdges, WORD v0, WORD v1 ){    // Remove interior edges (which appear in the list twice)    for( DWORD i = 0; i < dwNumEdges; ++i )    {        if( ( pEdges[2*i+0] == v0 && pEdges[2*i+1] == v1 ) ||            ( pEdges[2*i+0] == v1 && pEdges[2*i+1] == v0 ) )        {            if( dwNumEdges > 1 )            {                pEdges[2*i+0] = pEdges[2*(dwNumEdges-1)+0];                pEdges[2*i+1] = pEdges[2*(dwNumEdges-1)+1];            }            --dwNumEdges;            return;        }    }    pEdges[2*dwNumEdges+0] = v0;    pEdges[2*dwNumEdges+1] = v1;    dwNumEdges++;}
Q: How does a UNIX Guru do sex? A: unzip; strip; touch; finger; mount; fschk; more; yes; umount; sleep " -- Martin Kahlert
First I have to mention that the definition of the light source you are using is confused. When looking at the code of the "main loop" it seems that the light is a _point source_; if so, it should be emitting light in all directions with a constant distribution originating at the light's location. On the other hand, inside the buildShadowVolume routine, it is more or less handled like a _directed source_, since you use the light's position like a direction vector equally for all vertices. That does match only in a very tight range of situations and should be clarified!

Please think of the difference of position vectors and direction vectors: In a well defined geometric algebra you could not subtract positions to yield a new position (e.g. look at the homogenous co-ordinate w: what you request is 1-1=1!). What you actually want to do is to add a direction vector to the position of the particular vertex, to get the projected vertex away from the light (homogeneous w: 1+0=1). The direction vector to add is either the difference of the vertex position and the light position (homogeneous w: 1-1=0) in case of a point light, or else the given direction vector of a directed light (homogeneous w: is 0). As you can see here, the math uniquely defines the way to work, so it really helps if thinking in the distinct kinds of vectors...

Next, I still don't understand why the extrusion of the model's vertices is done towards the light source, instead of away from it: In the buildShadowVolume routine the light vector is _subtracted_ from the particular model vertex. However, you told already that the shadow works well as long as the crate isn't moved, but damned, I don't understand how it does :-(

Now to the current problem analysis. Due to the code of the "main loop" and the buildShadowVolume routine it seems to me that the shadow volume is computed in model space. The model space and the global space are identical as long as the crate isn't moved, since the crate is originally located at (0,0,0) and not rotated. So, if the rendering pipeline is set up for global or else for local rendering, all looks fine when rendering the shadow. But it doesn't anymore if the local space is changed. You said that the shadow remains in place even if the local space changes, so I assume the rendering set-up for the shadow is for global shadows, not for local shadows. As far as I can see, the posted code snippets does not show this aspect of the rendering set-up (but I must repeat that I'm not firm w/ D3D, so I may miss something).

Whow, a slightly lenghty text, I know. But due to the things mentioned above I'm currently not able to post some code.

EDIT: In the first paragraph I meant a constant distribution in angular direction, not in radial direction.

[Edited by - haegarr on November 9, 2005 2:55:17 PM]
Hehe yeah the light CAN be confusing.

Think of he pointlight as this: A light bulb, that i think might clear some of ur confusiness! ;P

Well yes, i agree, i think i have to implement a code that changes the shadow aspect depending on the objects position. How that should be done is another chapter... =) BUT i have another Code snippet of stencil buffer that renders shadow AND moves it along with the object BUT doesn't changes in align depending on the light source so it remains as if the shadow was from where it once was rendered: The code is slightly simplier:
void RenderShadow(){	Device->SetRenderState(D3DRS_STENCILENABLE,    true);    Device->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_EQUAL);    Device->SetRenderState(D3DRS_STENCILREF,       0x0);    Device->SetRenderState(D3DRS_STENCILMASK,      0xffffffff);    Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);    Device->SetRenderState(D3DRS_STENCILZFAIL,     D3DSTENCILOP_KEEP);    Device->SetRenderState(D3DRS_STENCILFAIL,      D3DSTENCILOP_KEEP);    Device->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_INCR); // increment to 1	// position shadow	D3DXVECTOR4 lightDirection(plight1.Position.x, plight1.Position.y, plight1.Position.z, 0.0f);	D3DXPLANE groundPlane(0.0f, 0.1f, 0.0f, 0.0f);	D3DXMATRIX S;	D3DXMatrixShadow(		&S,		&lightDirection,		&groundPlane);	D3DXMATRIX T;	D3DXMatrixTranslation(		&T,		Boxposition[0],		Boxposition[1],		Boxposition[2]);	D3DXMATRIX W = T * S;	Device->SetTransform(D3DTS_WORLD, &W);	// alpha blend the shadow	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);	Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);	Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);	D3DMATERIAL9 mtrl = d3d::InitMtrl(d3d::BLACK, d3d::BLACK, d3d::BLACK, d3d::BLACK, 0.0f);	mtrl.Diffuse.a = 0.5f; // 50% transparency.	// Disable depth buffer so that z-fighting doesn't occur when we	// render the shadow on top of the floor.	Device->SetRenderState(D3DRS_ZENABLE, false);	Device->SetMaterial(&mtrl);	Device->SetTexture(0, 0);	Crate->DrawSubset(0);	Device->SetRenderState(D3DRS_ZENABLE, true);	Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);	Device->SetRenderState(D3DRS_STENCILENABLE,    false);}


And the result:

Object Not moved!
Object Moved!
Q: How does a UNIX Guru do sex? A: unzip; strip; touch; finger; mount; fschk; more; yes; umount; sleep " -- Martin Kahlert
Well, I actually _have_ missed something: the render routine _does_ set the trafo to the space of the crate. Sorry for that.
Quote:Original post by haegarr
Well, I actually _have_ missed something: the render routine _does_ set the trafo to the space of the crate. Sorry for that.


xP

Quote:
Hi,

if m_pShadowVolume->buildShadowVolume(Crate, vLightInObjectSpace); is the line that build the shadow volume, then the error probably comes from this method. So post the code and we'll be able the spot it ^^
For your problem, you have to transform the vertices used for the shadow extrusion by the model matrix.

And for the problem with the "another Shadow Example" ... I don't understand it ^^ Try posting a screenshot ^^


Done! ;)

A picture says more then 1000 words... is hat why we like screenshots? ;)

Oh BTW, here's a screenie of the shadow with my actual code i'm using right now (the one i posted first):

Moved!

[Edited by - Christian Johansson on November 9, 2005 3:45:11 PM]
Q: How does a UNIX Guru do sex? A: unzip; strip; touch; finger; mount; fschk; more; yes; umount; sleep " -- Martin Kahlert
Quote:Original post by Christian Johansson
Think of he pointlight as this: A light bulb, that i think might clear some of ur confusiness! ;P

Nope, it doesn't. A light bulb is modeled often as a point light (what actually discards the penumbra shadows, but makes the life much easier). That is actually the assumption I've already made. However, you don't compute the shadow volume for a light bulb but for something like a _sun_.

The difference is as follows: The light bulb is positioned in the room of your scenery, e.g. a latern dangling from the ceiling. The bulb radiates in all directions, so it illuminates all walls, the floor and the ceiling. The light is emitted _spherically_ from the idealized point of the light. Moving the light source around makes a difference, at least if the light is moved out of the room ;-)

In comparison the sun is far away from the earth. Moving the sun a kilometer in any direction makes no difference here on earth, so that the position of the sun (as a light source) plays no role. The light rays coming here are nearly _parallel_. So a sun could be modeled as a pure directional (parallel) light source.

For the projection of the model vertices one needs the "ray" of a light from the source to the vertex. Expressed in code, it may look like this:
// a light basis classclass Light {public:   virtual void getProjectorForVertex(const Vector& positionOfVertex,Vector& projector) =0;};// a point light, as a model of a light bulbclass PointLight : public Light {public:   Vector ownPosition; // a position vector !!   void getProjectorForVertex(const Vector& positionOfVertex,Vector& projector) {      projector.x = positionOfVertex.x - ownPosition.x;      projector.y = positionOfVertex.y - ownPosition.y;      projector.z = positionOfVertex.z - ownPosition.z;      // here should possibly be done some normalization   }};// a pure directed light, as a model of the sun (as long as staying on earth)class DirectedLight : public Light {public:   Vector ownDirection; // a direction vector !!   void getProjectorForVertex(const Vector& positionOfVertex,Vector& projector) {      projector.x = ownDirection.x;      projector.y = ownDirection.y;      projector.z = ownDirection.z;   }};

When applying this stuff to a given vertex position P, one has to get the projector for the vertex P, and to add the projector to P. So one gets
P1' := P + d in case of a directed light w/ direction d, and
P2' := P + ( P - L ) in case of a point light w/ position L.

Since P isn't constant, the (direction) vector P-L isn't constant, too. In opposite to d, that actually _is_ constant. So there is definitely a difference in the two light sources.

Now look at your code. You compute the projected vertex like
P' := P - 500*L
Besides the fact that you scale and subtract a position vector (what I've said it earlier, has no geometrical meaning), the _structure_ of this formula is that of P1', and also L is a constant as is d of P1'! So you actually do _not_ compute a lighting as from a bulb, but from a directed source!!!

EDIT: Please bear in mind that the spaces of the model and the lights must be the same; I haven't shown that in the snippet above for clarity (and convenience ;-)

EDIT: To make clear what the geometrical difference is: If a ball is lit by the sun, it casts a (nearly) cylindrical shadow. If, on the other hand, the ball is lit by a point light, it casts a conical shadow. At least if the point light is moved to "infinity" (where the sun approximately is), the difference of a point light and a pure directed light disappears!

[Edited by - haegarr on November 10, 2005 2:26:41 AM]
Yeah i know that, BUT it doesn't matter WHAT i set as a light it can be a cube a sphere or a Car, it's just numbers of a position, however that position is based upon the lightsources position so that we think that "the light casts a shadow" ;P

This confusing me just MORE =P

But i managed to get some progress, i managed to move my object AND the shadow, but it remains as in picture number 2 (Moved!) =)

Here's what i've added:

inside the part where the shadow is calculated and drawn:
[SOURCE]D3DXMatrixIdentity( &m_matCrate );			D3DXVECTOR3 vLightInWorldSpace(plight1.Position.x, plight1.Position.y, plight1.Position.z);			D3DXVECTOR3 bposition(Boxposition[0],Boxposition[1]-2,Boxposition[2]);			D3DXVECTOR3 vLightInObjectSpace;			D3DXMatrixMultiply(&m_matCrate, &m_matCrate, &W); // _NEW_			D3DXMATRIXA16 matInverse;			D3DXMatrixInverse( &matInverse, NULL, &m_matCrate );			D3DXVec3TransformNormal( &vLightInObjectSpace, &vLightInWorldSpace, &matInverse );			m_pShadowVolume-&gt;render(Device); // _NEW_			Device-&gt;SetTransform(D3DTS_WORLD, &m_matCrate);// _NEW_			m_pShadowVolume-&gt;buildShadowVolume( Crate, vLightInObjectSpace );[/SOURCE]


Damn i got to read urs twice before i'm getting it! =P What are u doing to my brain? *L*

;)

[Edited by - Christian Johansson on November 9, 2005 4:43:40 PM]
Q: How does a UNIX Guru do sex? A: unzip; strip; touch; finger; mount; fschk; more; yes; umount; sleep " -- Martin Kahlert

This topic is closed to new replies.

Advertisement