438 lines
14 KiB
C++
438 lines
14 KiB
C++
|
//
|
||
|
//
|
||
|
// win_stencilshadow.cpp
|
||
|
//
|
||
|
// Stencil shadow computation/rendering
|
||
|
//
|
||
|
//
|
||
|
|
||
|
#include "../server/exe_headers.h"
|
||
|
|
||
|
#include "../renderer/tr_local.h"
|
||
|
#include "../renderer/tr_lightmanager.h"
|
||
|
#include "glw_win_dx8.h"
|
||
|
#include "win_local.h"
|
||
|
|
||
|
#include "win_stencilshadow.h"
|
||
|
|
||
|
|
||
|
StencilShadow StencilShadower;
|
||
|
|
||
|
|
||
|
StencilShadow::StencilShadow()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
StencilShadow::~StencilShadow()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
void StencilShadow::AddEdge( int i1, int i2, int facing )
|
||
|
{
|
||
|
int c;
|
||
|
|
||
|
c = m_numEdgeDefs[ i1 ];
|
||
|
if ( c == MAX_EDGE_DEFS )
|
||
|
{
|
||
|
Com_Printf("WARNING: MAX_EDGE_DEFS overflow!\n");
|
||
|
return; // overflow
|
||
|
}
|
||
|
m_edgeDefs[ i1 ][ c ].i2 = i2;
|
||
|
m_edgeDefs[ i1 ][ c ].facing = facing;
|
||
|
|
||
|
m_numEdgeDefs[ i1 ]++;
|
||
|
}
|
||
|
|
||
|
|
||
|
void StencilShadow::RenderEdges()
|
||
|
{
|
||
|
// int i;
|
||
|
//int c, c2;
|
||
|
//int j, k;
|
||
|
//int i2;
|
||
|
//int c_edges, c_rejected;
|
||
|
//int hit[2];
|
||
|
|
||
|
//// an edge is NOT a silhouette edge if its face doesn't face the light,
|
||
|
//// or if it has a reverse paired edge that also faces the light.
|
||
|
//// A well behaved polyhedron would have exactly two faces for each edge,
|
||
|
//// but lots of models have dangling edges or overfanned edges
|
||
|
//c_edges = 0;
|
||
|
//c_rejected = 0;
|
||
|
|
||
|
//for ( i = 0 ; i < tess.numVertexes ; i++ )
|
||
|
//{
|
||
|
// c = m_numEdgeDefs[ i ];
|
||
|
// for ( j = 0 ; j < c ; j++ )
|
||
|
// {
|
||
|
// if ( !m_edgeDefs[ i ][ j ].facing )
|
||
|
// {
|
||
|
// continue;
|
||
|
// }
|
||
|
|
||
|
// hit[0] = 0;
|
||
|
// hit[1] = 0;
|
||
|
|
||
|
// i2 = m_edgeDefs[ i ][ j ].i2;
|
||
|
// c2 = m_numEdgeDefs[ i2 ];
|
||
|
// for ( k = 0 ; k < c2 ; k++ )
|
||
|
// {
|
||
|
// if ( m_edgeDefs[ i2 ][ k ].i2 == i )
|
||
|
// {
|
||
|
// hit[ m_edgeDefs[ i2 ][ k ].facing ]++;
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// // if it doesn't share the edge with another front facing
|
||
|
// // triangle, it is a sil edge
|
||
|
// if ( hit[ 1 ] == 0 )
|
||
|
// {
|
||
|
// VectorCopy( tess.xyz[i], m_shadowVerts[0] );
|
||
|
// VectorCopy( tess.xyz[i + tess.numVertexes], m_shadowVerts[1] );
|
||
|
// VectorCopy( tess.xyz[i2], m_shadowVerts[2] );
|
||
|
// VectorCopy( tess.xyz[i2 + tess.numVertexes], m_shadowVerts[3] );
|
||
|
|
||
|
// c_edges++;
|
||
|
|
||
|
// glw_state->device->SetVertexShader( D3DFVF_XYZ );
|
||
|
// glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, m_shadowVerts, sizeof(vec3_t) );
|
||
|
// }
|
||
|
// else
|
||
|
// {
|
||
|
// c_rejected++;
|
||
|
// }
|
||
|
// }
|
||
|
//}
|
||
|
|
||
|
int i;
|
||
|
int c;
|
||
|
int j;
|
||
|
int i2;
|
||
|
int c_edges, c_rejected;
|
||
|
int numTris;
|
||
|
int o1, o2, o3;
|
||
|
|
||
|
// an edge is NOT a silhouette edge if its face doesn't face the light,
|
||
|
// or if it has a reverse paired edge that also faces the light.
|
||
|
// A well behaved polyhedron would have exactly two faces for each edge,
|
||
|
// but lots of models have dangling edges or overfanned edges
|
||
|
c_edges = 0;
|
||
|
c_rejected = 0;
|
||
|
|
||
|
int nVerts = 0, numPrims = 0;
|
||
|
|
||
|
for ( i = 0 ; i < tess.numVertexes ; i++ )
|
||
|
{
|
||
|
c = m_numEdgeDefs[ i ];
|
||
|
for ( j = 0 ; j < c ; j++ )
|
||
|
{
|
||
|
if ( !m_edgeDefs[ i ][ j ].facing )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//with this system we can still get edges shared by more than 2 tris which
|
||
|
//produces artifacts including seeing the shadow through walls. So for now
|
||
|
//we are going to render all edges even though it is a tiny bit slower. -rww
|
||
|
i2 = m_edgeDefs[ i ][ j ].i2;
|
||
|
VectorCopy( tess.xyz[i], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[i + tess.numVertexes], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[i2 + tess.numVertexes], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[i2], m_shadowVerts[nVerts++] );
|
||
|
numPrims++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!numPrims || !nVerts)
|
||
|
return;
|
||
|
|
||
|
glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE );
|
||
|
|
||
|
glw_state->device->SetVertexShader( D3DFVF_XYZ );
|
||
|
glw_state->device->DrawPrimitiveUP( D3DPT_QUADLIST, numPrims, m_shadowVerts, sizeof(vec3_t) );
|
||
|
|
||
|
nVerts = 0;
|
||
|
numPrims = 0;
|
||
|
|
||
|
//Carmack Reverse<tm> method requires that volumes
|
||
|
//be capped properly -rww
|
||
|
numTris = tess.numIndexes / 3;
|
||
|
|
||
|
for ( i = 0 ; i < numTris ; i++ )
|
||
|
{
|
||
|
if ( !m_facing[i] )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
o1 = tess.indexes[ i*3 + 0 ];
|
||
|
o2 = tess.indexes[ i*3 + 1 ];
|
||
|
o3 = tess.indexes[ i*3 + 2 ];
|
||
|
|
||
|
VectorCopy( tess.xyz[o1], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[o2], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[o3], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[o3 + tess.numVertexes], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[o2 + tess.numVertexes], m_shadowVerts[nVerts++] );
|
||
|
VectorCopy( tess.xyz[o1 + tess.numVertexes], m_shadowVerts[nVerts++] );
|
||
|
numPrims += 2;
|
||
|
}
|
||
|
|
||
|
glw_state->device->SetVertexShader( D3DFVF_XYZ );
|
||
|
glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, numPrims, m_shadowVerts, sizeof(vec3_t) );
|
||
|
}
|
||
|
|
||
|
|
||
|
bool StencilShadow::BuildFromLight( VVdlight_t *dl )
|
||
|
{
|
||
|
// int i;
|
||
|
//int numTris;
|
||
|
//vec3_t lightDir;
|
||
|
//D3DXMATRIX matWorldInv;
|
||
|
//D3DXVECTOR4 viewLightPos;
|
||
|
|
||
|
//// we can only do this if we have enough space in the vertex buffers
|
||
|
//if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) {
|
||
|
// return false;
|
||
|
//}
|
||
|
|
||
|
//// project vertexes away from light direction
|
||
|
//for ( i = 0 ; i < tess.numVertexes ; i++ )
|
||
|
//{
|
||
|
// // Get the light direction to the vertex
|
||
|
// VectorCopy( backEnd.currentEntity->lightDir, lightDir );
|
||
|
|
||
|
// VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] );
|
||
|
//}
|
||
|
|
||
|
int i;
|
||
|
int numTris;
|
||
|
vec3_t lightDir, ground;
|
||
|
float d;
|
||
|
|
||
|
// we can only do this if we have enough space in the vertex buffers
|
||
|
if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//controlled method - try to keep shadows in range so they don't show through so much -rww
|
||
|
vec3_t worldxyz, ld;
|
||
|
float groundDist, extlength;
|
||
|
|
||
|
VectorCopy( backEnd.currentEntity->lightDir, lightDir );
|
||
|
|
||
|
ground[0] = backEnd.ori.axis[0][2];
|
||
|
ground[1] = backEnd.ori.axis[1][2];
|
||
|
ground[2] = backEnd.ori.axis[2][2];
|
||
|
|
||
|
d = DotProduct( lightDir, ground );
|
||
|
// don't let the shadows get too long or go negative
|
||
|
if ( d < 0.5 ) {
|
||
|
VectorMA( lightDir, (0.5 - d), ground, lightDir );
|
||
|
d = DotProduct( lightDir, ground );
|
||
|
}
|
||
|
d = 1.0 / d;
|
||
|
|
||
|
lightDir[0] = lightDir[0] * d;
|
||
|
lightDir[1] = lightDir[1] * d;
|
||
|
lightDir[2] = lightDir[2] * d;
|
||
|
|
||
|
VectorNormalize(lightDir);
|
||
|
|
||
|
//Oh well, just cast them straight down no matter what onto the ground plane.
|
||
|
//This presents no chance of screwups and still looks better than a stupid
|
||
|
//shader blob.
|
||
|
//VectorSet(lightDir, 0.0f, 0.0f, 1.0f);
|
||
|
|
||
|
// project vertexes away from light direction
|
||
|
for ( i = 0 ; i < tess.numVertexes ; i++ ) {
|
||
|
//add or.origin to vert xyz to end up with world oriented coord, then figure
|
||
|
//out the ground pos for the vert to project the shadow volume to
|
||
|
//VectorAdd(tess.xyz[i], backEnd.ori.origin, worldxyz);
|
||
|
//groundDist = worldxyz[2] - backEnd.currentEntity->e.shadowPlane;
|
||
|
//groundDist += 2.0f; //fudge factor
|
||
|
//VectorMA( tess.xyz[i], -groundDist, lightDir, tess.xyz[i+tess.numVertexes] );
|
||
|
VectorMA( tess.xyz[i], -200.0f, lightDir, tess.xyz[i+tess.numVertexes] );
|
||
|
}
|
||
|
|
||
|
|
||
|
// decide which triangles face the light
|
||
|
memset( m_numEdgeDefs, 0, 4 * tess.numVertexes );
|
||
|
|
||
|
numTris = tess.numIndexes / 3;
|
||
|
for ( i = 0 ; i < numTris ; i++ )
|
||
|
{
|
||
|
int i1, i2, i3;
|
||
|
vec3_t d1, d2, normal;
|
||
|
float *v1, *v2, *v3;
|
||
|
float d;
|
||
|
|
||
|
i1 = tess.indexes[ i*3 + 0 ];
|
||
|
i2 = tess.indexes[ i*3 + 1 ];
|
||
|
i3 = tess.indexes[ i*3 + 2 ];
|
||
|
|
||
|
v1 = tess.xyz[ i1 ];
|
||
|
v2 = tess.xyz[ i2 ];
|
||
|
v3 = tess.xyz[ i3 ];
|
||
|
|
||
|
VectorSubtract( v2, v1, d1 );
|
||
|
VectorSubtract( v3, v1, d2 );
|
||
|
CrossProduct( d1, d2, normal );
|
||
|
|
||
|
d = DotProduct( normal, lightDir );
|
||
|
if ( d > 0 ) {
|
||
|
m_facing[ i ] = 1;
|
||
|
} else {
|
||
|
m_facing[ i ] = 0;
|
||
|
}
|
||
|
|
||
|
// create the edges
|
||
|
AddEdge( i1, i2, m_facing[ i ] );
|
||
|
AddEdge( i2, i3, m_facing[ i ] );
|
||
|
AddEdge( i3, i1, m_facing[ i ] );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void StencilShadow::RenderShadow()
|
||
|
{
|
||
|
DWORD lighting, fog, srcblend, destblend, alphablend, zwrite, zfunc;
|
||
|
|
||
|
glw_state->device->GetRenderState( D3DRS_LIGHTING, &lighting );
|
||
|
glw_state->device->GetRenderState( D3DRS_FOGENABLE, &fog );
|
||
|
glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend );
|
||
|
glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend );
|
||
|
glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend );
|
||
|
glw_state->device->GetRenderState( D3DRS_ZWRITEENABLE, &zwrite );
|
||
|
glw_state->device->GetRenderState( D3DRS_ZFUNC, &zfunc );
|
||
|
|
||
|
GL_Bind( tr.whiteImage );
|
||
|
|
||
|
glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE );
|
||
|
glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE );
|
||
|
|
||
|
// Disable z-buffer writes (note: z-testing still occurs), and enable the
|
||
|
// stencil-buffer
|
||
|
glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILENABLE, TRUE );
|
||
|
|
||
|
// Don't bother with interpolating color
|
||
|
glw_state->device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT );
|
||
|
|
||
|
glw_state->device->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESS );
|
||
|
|
||
|
// Set up stencil compare function, 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.
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_INCR );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP );
|
||
|
|
||
|
// If ztest passes, inc/decrement stencil buffer value
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILREF, 0x1 );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILMASK, 0xffffffff );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP );
|
||
|
|
||
|
// Make sure that no pixels get drawn to the frame buffer
|
||
|
glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
|
||
|
glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO );
|
||
|
glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
|
||
|
|
||
|
glw_state->device->SetTransform(D3DTS_VIEW,
|
||
|
glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop());
|
||
|
|
||
|
glw_state->device->SetTexture(0, NULL);
|
||
|
glw_state->device->SetTexture(1, NULL);
|
||
|
|
||
|
qglCullFace( GL_FRONT );
|
||
|
|
||
|
// Draw front-side of shadow volume in stencil/z only
|
||
|
RenderEdges();
|
||
|
|
||
|
// Now reverse cull order so back sides of shadow volume are written.
|
||
|
qglCullFace( GL_BACK );
|
||
|
|
||
|
// Decrement stencil buffer value
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_DECR );
|
||
|
|
||
|
// Draw back-side of shadow volume in stencil/z only
|
||
|
RenderEdges();
|
||
|
|
||
|
// Restore render states
|
||
|
glw_state->device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE );
|
||
|
glw_state->device->SetRenderState( D3DRS_LIGHTING, lighting );
|
||
|
glw_state->device->SetRenderState( D3DRS_FOGENABLE, fog );
|
||
|
glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend );
|
||
|
glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend );
|
||
|
glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend );
|
||
|
glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, zwrite );
|
||
|
glw_state->device->SetRenderState( D3DRS_ZFUNC, zfunc );
|
||
|
glw_state->device->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
|
||
|
}
|
||
|
|
||
|
|
||
|
void StencilShadow::FinishShadows()
|
||
|
{
|
||
|
DWORD lighting, fog, srcblend, destblend, alphablend;
|
||
|
|
||
|
glw_state->device->GetRenderState( D3DRS_LIGHTING, &lighting );
|
||
|
glw_state->device->GetRenderState( D3DRS_FOGENABLE, &fog );
|
||
|
glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend );
|
||
|
glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend );
|
||
|
glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend );
|
||
|
|
||
|
// The stencilbuffer values indicates # of shadows that overlap each pixel.
|
||
|
// We only want to draw pixels that are in shadow, which was set up in
|
||
|
// RenderShadow() such that StencilBufferValue >= 1. In the Direct3D API,
|
||
|
// the stencil test is pseudo coded as:
|
||
|
// StencilRef CompFunc StencilBufferValue
|
||
|
// so we set our renderstates with StencilRef = 1 and CompFunc = LESSEQUAL.
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILENABLE, TRUE );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILREF, 0);//0x1 );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL);//D3DCMP_LESSEQUAL );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 255 );
|
||
|
|
||
|
// Set renderstates (disable z-buffering and turn on alphablending)
|
||
|
glw_state->device->SetRenderState( D3DRS_ZENABLE, FALSE );
|
||
|
glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
|
||
|
glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
|
||
|
glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
|
||
|
|
||
|
// Set the hardware to draw black, alpha-blending pixels
|
||
|
glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
|
||
|
glw_state->device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TFACTOR );
|
||
|
glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );
|
||
|
glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR );
|
||
|
glw_state->device->SetRenderState( D3DRS_TEXTUREFACTOR, 0x7f000000 );
|
||
|
|
||
|
glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE );
|
||
|
|
||
|
// Draw the big, darkening square
|
||
|
static FLOAT v[4][4] =
|
||
|
{
|
||
|
{ 0 - 0.5f, 0 - 0.5f, 0.0f, 1.0f },
|
||
|
{ 640 - 0.5f, 0 - 0.5f, 0.0f, 1.0f },
|
||
|
{ 640 - 0.5f, 480 - 0.5f, 0.0f, 1.0f },
|
||
|
{ 0 - 0.5f, 480 - 0.5f, 0.0f, 1.0f },
|
||
|
};
|
||
|
|
||
|
glw_state->device->SetVertexShader( D3DFVF_XYZRHW );
|
||
|
glw_state->device->DrawPrimitiveUP( D3DPT_QUADLIST, 1, v, sizeof(v[0]) );
|
||
|
|
||
|
// Restore render states
|
||
|
glw_state->device->SetRenderState( D3DRS_ZENABLE, TRUE );
|
||
|
glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE );
|
||
|
glw_state->device->SetRenderState( D3DRS_LIGHTING, lighting );
|
||
|
glw_state->device->SetRenderState( D3DRS_FOGENABLE, fog );
|
||
|
glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend );
|
||
|
glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend );
|
||
|
glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend );
|
||
|
}
|