Harald JOHNSEN:

Changes
=======

New volumetric shadows for FlightGear.

There is now two new checkboxes in the rendering dialog to enable/disable shadows
for the user aircraft and for static scenery objects (ie those defined in the .stg files).
AI and random objects are not handled for the moment.


known bugs
==========
- ghost objects
This commit is contained in:
ehofman
2005-06-26 17:16:45 +00:00
parent 8e0ecbeb8f
commit f62ff0ad66
4 changed files with 1050 additions and 2 deletions
+4
View File
@@ -24,7 +24,9 @@
#include <simgear/compiler.h>
#include STL_STRING
#include <vector>
SG_USING_STD(vector);
SG_USING_STD(string);
class SGLightning;
@@ -217,6 +219,8 @@ public:
void getFOV( float &w, float &h );
list_of_SGWxRadarEcho *get_radar_echo(void);
sgMat4 *get_transform(void) { return &transform; }
};
extern SGEnviro sgEnviro;
+4 -2
View File
@@ -12,7 +12,8 @@ include_HEADERS = \
modellib.hxx \
personality.hxx \
placement.hxx \
placementtrans.hxx
placementtrans.hxx \
shadowvolume.hxx
libsgmodel_a_SOURCES = \
animation.cxx \
@@ -22,6 +23,7 @@ libsgmodel_a_SOURCES = \
modellib.cxx \
personality.cxx \
placement.cxx \
placementtrans.cxx
placementtrans.cxx \
shadowvolume.cxx
INCLUDES = -I$(top_srcdir)
+900
View File
@@ -0,0 +1,900 @@
// Shadow volume class
//
// Written by Harald JOHNSEN, started June 2005.
//
// Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
//
//
#include <plib/sg.h>
#include <plib/ssg.h>
#include <simgear/props/props.hxx>
#include <simgear/debug/logstream.hxx>
#include SG_GLU_H
#include "shadowvolume.hxx"
/*
geometry and edge list
- traverse object graph until leaf to get geometry
- what about transform and selection ?
- use sub objects rather then objects
- get local transform and selection
- range anim : ssgRangeSelector ( min, max ) => ssgSelector ( true/false )
=> selection [0..n], kids [0..n]
- select/timed anim : ssgSelector ( true/false )
=> isSelected( nkid )
- spin/rotate/trans/scale/... : ssgTransform ( matrix )
=> getNetTransform
- on new object :
- for one object branch
- for each leaf
- save geometry
- save address in cache
- when rendering object
- for each leaf
- getNetTransform + object global rotation (ac) => transform for light position
- go up in tree and check isSelected( self )
- generate connectivity each time the geometry change
=> using local light so connectivity never changes
- generate active edge list when :
- light moves
- subpart moves (animation code)
- ac rotate
=> cache rotation matrix and generate edge list only if something change
=> even if it changes, no need to do that every frame
- static objects have static edge list if light does not move
shadowing a scene
- render full scene as normal
- render occluder in stencil buffer with their shadow volumes
- apply stencil to framebuffer (darkens shadowed parts)
*/
// TODO
// - shadow for objects
// * aircraft
// * tile objects (from .stg)
// - ai objects
// - random objects => tie shadow geometry to lib objects and reuse them
// - zfail if camera inside shadow
// - queue geometry work if lot of objects
// * add a render property on/off (for aircraft, for scene objects, for ai)
// * add a render property in rendering dialog
// * filter : halo, light, shadow, disc, disk, flame, (exhaust), noshadow
// BUG
// - ghost objects ?
// - no shadow : check static 737/747
// why has the ggb an alpha animation = 0.01 ?
static int statSilhouette=0;
static int statGeom=0;
static int statObj=0;
static SGShadowVolume *states;
SGShadowVolume::ShadowCaster::ShadowCaster( int _num_tri, ssgBranch * _geometry_leaf ) :
geometry_leaf ( _geometry_leaf ),
scenery_object ( 0 ),
first_select ( 0 ),
frameNumber ( 0 ),
numTriangles ( 0 ),
indices ( 0 ),
vertices ( 0 ),
planeEquations ( 0 ),
isFacingLight ( 0 ),
neighbourIndices ( 0 ),
isSilhouetteEdge ( 0 )
{
int num_tri = _num_tri;
numTriangles = num_tri;
indices = new int[num_tri * 3];
vertices = new sgVec3[num_tri * 3];
planeEquations = new sgVec4[num_tri];
neighbourIndices = new int[num_tri * 3];
isSilhouetteEdge = new bool[num_tri * 3];
isFacingLight = new bool[num_tri];
sgSetVec3(last_lightpos, 0.0, 0.0, 0.0);
statGeom ++;
ssgBranch *branch = (ssgBranch *) _geometry_leaf;
while( branch && branch->getNumParents() > 0 ) {
if( !first_select && branch->isA(ssgTypeSelector())) {
first_select = branch;
break;
}
branch = branch->getParent(0);
}
}
void SGShadowVolume::ShadowCaster::addLeaf( int & tri_idx, ssgLeaf *geometry_leaf ) {
int num_tri = geometry_leaf->getNumTriangles();
for(int i = 0; i < num_tri ; i ++ ) {
short v1, v2, v3;
sgVec3 a, b, c;
geometry_leaf->getTriangle( i, &v1, &v2, &v3 );
sgCopyVec3(a, geometry_leaf->getVertex(v1));
sgCopyVec3(b, geometry_leaf->getVertex(v2));
sgCopyVec3(c, geometry_leaf->getVertex(v3));
addTri( tri_idx++, a, b, c);
}
}
SGShadowVolume::ShadowCaster::~ShadowCaster() {
delete [] indices ;
delete [] vertices ;
delete [] planeEquations ;
delete [] isFacingLight ;
delete [] neighbourIndices ;
delete [] isSilhouetteEdge ;
}
void SGShadowVolume::ShadowCaster::addTri(int p, sgVec3 a, sgVec3 b, sgVec3 c) {
sgVec4 tri_plane;
assert( p >= 0 && p < numTriangles );
sgMakePlane ( tri_plane, a, b, c );
sgCopyVec4(planeEquations[p], tri_plane);
sgCopyVec3(vertices[p*3], a);
sgCopyVec3(vertices[p*3+1], b);
sgCopyVec3(vertices[p*3+2], c);
indices[p*3] = p*3;
indices[p*3+1] = p*3+1;
indices[p*3+2] = p*3+2;
}
bool SGShadowVolume::ShadowCaster::sameVertex(int edge1, int edge2) {
// const float epsilon = 0.01; // 1cm
// const float epsilon = 0.0; // 1cm
//return false;
if( edge1 == edge2)
return true;
sgVec3 delta_v;
sgSubVec3( delta_v, vertices[edge1], vertices[edge2]);
if( delta_v[SG_X] != 0.0) return false;
if( delta_v[SG_Y] != 0.0) return false;
if( delta_v[SG_Z] != 0.0) return false;
return true;
}
//Calculate neighbour faces for each edge
// caution, this is O(n2)
void SGShadowVolume::ShadowCaster::SetConnectivity(void)
{
int edgeCount = 0;
//set the neighbour indices to be -1
for(int ii=0; ii<numTriangles*3; ++ii)
neighbourIndices[ii]=-1;
//loop through triangles
for(int i=0; i<numTriangles-1; ++i)
{
//loop through edges on the first triangle
for(int edgeI=0; edgeI<3; ++edgeI)
{
//continue if this edge already has a neighbour set
if(neighbourIndices[ i*3+edgeI ]!=-1)
continue;
//get the vertex indices on each edge
int edgeI1=indices[i*3+edgeI];
int edgeI2=indices[i*3+(edgeI+1)%3];
//loop through triangles with greater indices than this one
for(int j=i+1; j<numTriangles; ++j)
{
//loop through edges on triangle j
for(int edgeJ=0; edgeJ<3; ++edgeJ)
{
//get the vertex indices on each edge
int edgeJ1=indices[j*3+edgeJ];
int edgeJ2=indices[j*3+(edgeJ == 2 ? 0 : edgeJ+1)];
//if these are the same (possibly reversed order), these faces are neighbours
//no, we only use reverse order because same order means that
//the triangle is wrongly oriented and that will cause shadow artifact
if( sameVertex(edgeI1, edgeJ1) && sameVertex(edgeI2, edgeJ2) ) {
// can happens with 'bad' geometry
// printf("flipped triangle detected...check your model\n");
continue;
}
if( false && sameVertex(edgeI1, edgeJ1) && sameVertex(edgeI2, edgeJ2)
|| sameVertex(edgeI1, edgeJ2) && sameVertex(edgeI2, edgeJ1) )
{
int edgeI3=indices[i*3+(edgeI+2)%3];
int edgeJ3=indices[j*3+(edgeJ+2)%3];
if( sameVertex(edgeI3, edgeJ3) ) {
// can happens with 'bad' geometry
// printf("duplicated tri...check your model\n");
// exit loop
break;
}
//continue if this edge already has a neighbour set
// can happens with 'bad' geometry
if(neighbourIndices[ j*3+edgeJ ]!=-1) {
// printf("bad edge detected\n");
continue;
}
neighbourIndices[i*3+edgeI]=j;
neighbourIndices[j*3+edgeJ]=i;
edgeCount ++;
// exit loop
j = numTriangles;
break;
}
}
}
}
}
// printf("submodel has %d triangles and %d shared edges\n", numTriangles, edgeCount);
}
//calculate silhouette edges
void SGShadowVolume::ShadowCaster::CalculateSilhouetteEdges(sgVec3 lightPosition)
{
//Calculate which faces face the light
for(int i=0; i<numTriangles; ++i)
{
if( sgDistToPlaneVec3 ( planeEquations[i], lightPosition ) > 0.0 )
isFacingLight[i]=true;
else
isFacingLight[i]=false;
}
//loop through faces
for(int t=0; t < numTriangles; t++) {
int v = t * 3;
//if this face is not facing the light, not a silhouette edge
if(!isFacingLight[t])
{
isSilhouetteEdge[v+0]=false;
isSilhouetteEdge[v+1]=false;
isSilhouetteEdge[v+2]=false;
continue;
}
//loop through edges
for(int i = v ; i < v+3 ; i++) {
//this face is facing the light
//if the neighbouring face is not facing the light, or there is no neighbouring face,
//then this is a silhouette edge
if(neighbourIndices[i]==-1 || !isFacingLight[neighbourIndices[i]])
isSilhouetteEdge[i]=true;
else
isSilhouetteEdge[i]=false;
}
}
}
// TODO: everyhting here is constant, store the vertex in a cache buffer and call drawelements
void SGShadowVolume::ShadowCaster::DrawInfiniteShadowVolume(sgVec3 lightPosition, bool drawCaps)
{
glColor4f(1.0, 1.0, 0.0, 0.5);
//TODO: no need for a quad here
// glBegin(GL_QUADS);
glBegin(GL_TRIANGLES);
{
for(int i=0; i<numTriangles; ++i)
{
//if this face does not face the light, continue
if(!isFacingLight[i])
continue;
int v = i*3;
//Loop through edges on this face
for(int j=0; j<3; ++j)
{
//Draw the shadow volume "edge" if this is a silhouette edge
if(isSilhouetteEdge[v+j])
{
sgVec3 vertex1, vertex2;
sgCopyVec3(vertex1, vertices[indices[v+j]]);
sgCopyVec3(vertex2, vertices[indices[v+(j == 2 ? 0 : j+1)]]);
glVertex3fv(vertex2);
glVertex3fv(vertex1);
// w == 0 for infinite shadow
#if 0
glVertex4f( vertex1[SG_X]-lightPosition[SG_X],
vertex1[SG_Y]-lightPosition[SG_Y],
vertex1[SG_Z]-lightPosition[SG_Z], 0.0f);
// glVertex4f( vertex2[SG_X]-lightPosition[SG_X],
// vertex2[SG_Y]-lightPosition[SG_Y],
// vertex2[SG_Z]-lightPosition[SG_Z], 0.0f);
#else
glVertex3f( vertex1[SG_X]-lightPosition[SG_X],
vertex1[SG_Y]-lightPosition[SG_Y],
vertex1[SG_Z]-lightPosition[SG_Z]);
// glVertex3f( vertex2[SG_X]-lightPosition[SG_X],
// vertex2[SG_Y]-lightPosition[SG_Y],
// vertex2[SG_Z]-lightPosition[SG_Z]);
#endif
}
}
}
}
glEnd();
//Draw caps if required
if(drawCaps)
{
glBegin(GL_TRIANGLES);
{
for(int i=0; i<numTriangles; ++i)
{
int v = i*3;
for(int j=0; j<3; ++j)
{
sgVec3 vertex;
sgCopyVec3(vertex, vertices[indices[v+j]]);
if(isFacingLight[i])
glVertex3fv(vertex);
else
glVertex4f( vertex[SG_X]-lightPosition[SG_X],
vertex[SG_Y]-lightPosition[SG_Y],
vertex[SG_Z]-lightPosition[SG_Z], 0.0f);
}
}
}
glEnd();
}
}
void SGShadowVolume::ShadowCaster::getNetTransform ( ssgBranch * branch, sgMat4 xform )
{
// one less matmult...
bool first = true;
// sgMakeIdentMat4 ( xform ) ;
while( branch && branch != lib_object ) {
if( branch->isA(ssgTypeTransform()) ) {
sgMat4 transform;
if( first ) {
((ssgTransform *) branch)->getTransform( xform );
first = false;
} else {
((ssgTransform *) branch)->getTransform(transform);
sgPostMultMat4 ( xform, transform ) ;
}
}
branch = branch->getParent( 0 );
}
if( first )
sgMakeIdentMat4 ( xform ) ;
}
// check the value of <select> and <range> animation
// wich have been computed during the rendering
bool SGShadowVolume::ShadowCaster::isSelected ( ssgBranch * branch ) {
while( branch && branch != lib_object) {
if( branch->isA(ssgTypeSelector()) )
if( !((ssgSelector *) branch)->isSelected(0) )
return false;
branch = branch->getParent(0);
}
return true;
}
/*
trans ----- personality ---- shared object
perso1 *-----
\
*--------* shared object
/
perso2 *-----
*/
void SGShadowVolume::ShadowCaster::computeShadows(sgMat4 rotation, sgMat4 rotation_translation) {
// check the select and range ssgSelector node
// object can't cast shadow if it is not visible
if( first_select && ! isSelected( first_select) )
return;
sgMat4 transform ;
sgMat4 invTransform;
// get the transformations : this comes from animation code for example
// or static transf
getNetTransform( (ssgBranch *) geometry_leaf, transform );
sgMat4 transf;
sgCopyMat4( transf, transform );
sgPostMultMat4( transf, rotation_translation );
sgPostMultMat4( transform, rotation );
// sgInvertMat4( invTransform, transform );
sgTransposeNegateMat4 ( invTransform, transform );
glLoadMatrixf ( (float *) states->CameraViewM ) ;
glMultMatrixf( (float *) transf );
sgVec3 lightPos;
sgCopyVec3( lightPos, states->sunPos );
sgXformPnt3( lightPos, invTransform );
sgVec3 lightPosNorm;
sgNormaliseVec3( lightPosNorm, lightPos );
float deltaPos = sgAbs(lightPosNorm[0] - last_lightpos[0]) +
sgAbs(lightPosNorm[1] - last_lightpos[1]) +
sgAbs(lightPosNorm[2] - last_lightpos[2]);
// if the geometry has rotated/moved enought then
// we need to recompute the silhouette
// but this computation does not need to be done each frame
// -6 fps
if( (deltaPos > 0.0) && ( states->frameNumber - frameNumber > 0)) {
CalculateSilhouetteEdges( lightPos );
sgCopyVec3( last_lightpos, lightPosNorm );
frameNumber = states->frameNumber ;
statSilhouette ++;
}
if( states->shadowsDebug_enabled )
{
glStencilFunc(GL_ALWAYS, 0, ~0);
glDisable( GL_CULL_FACE );
glDisable( GL_DEPTH_TEST );
glDisable(GL_STENCIL_TEST);
glColorMask(1, 1, 1, 1);
glColor4f(0.0, 0.0, 1.0, 0.8);
glBegin(GL_LINES);
for(int i=0; i<numTriangles; ++i)
{
if(!isFacingLight[i])
continue;
int v = i*3;
//Loop through edges on this face
sgVec3 vertex1, vertex2, vertex3;
sgCopyVec3(vertex1, vertices[indices[v+0]]);
sgCopyVec3(vertex2, vertices[indices[v+1]]);
sgCopyVec3(vertex3, vertices[indices[v+2]]);
if(isSilhouetteEdge[i*3+0]) {
glVertex3fv(vertex2);
glVertex3fv(vertex1);
}
if(isSilhouetteEdge[i*3+1]) {
glVertex3fv(vertex2);
glVertex3fv(vertex3);
}
if(isSilhouetteEdge[i*3+2]) {
glVertex3fv(vertex3);
glVertex3fv(vertex1);
}
}
glEnd();
glColorMask(0, 0, 0, 0);
glEnable( GL_CULL_FACE );
glEnable( GL_DEPTH_TEST );
glEnable(GL_STENCIL_TEST);
}
// -11 fps
// TODO:compute intersection with near clip plane...
bool needZFail=false;
// GL_INCR_WRAP_EXT, GL_DECR_WRAP_EXT
if(needZFail)
{
//Increment stencil buffer for back face depth fail
glStencilFunc(GL_ALWAYS, 0, ~0);
glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
glCullFace(GL_FRONT);
DrawInfiniteShadowVolume( lightPos, true);
//Decrement stencil buffer for front face depth fail
glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
glCullFace(GL_BACK);
DrawInfiniteShadowVolume( lightPos, true);
}
else //using zpass
{
//Increment stencil buffer for front face depth pass
glStencilFunc(GL_ALWAYS, 0, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glCullFace(GL_BACK);
DrawInfiniteShadowVolume( lightPos, false);
//Decrement stencil buffer for back face depth pass
glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
glCullFace(GL_FRONT);
DrawInfiniteShadowVolume( lightPos, false);
}
// -15 fps
}
void SGShadowVolume::SceneryObject::computeShadows(void) {
bool intersect = true;
// compute intersection with view frustum
// use pending_object (pointer on obj transform) & tile transform
// to get position
sgMat4 position, CamXpos;
sgFrustum *f = ssgGetFrustum();
pending_object->getParent(0)->getNetTransform( position );
sgCopyMat4 ( CamXpos, states->CameraViewM ) ;
sgPreMultMat4 ( CamXpos, position ) ;
sgSphere tmp = *(pending_object->getBSphere()) ;
if ( tmp.isEmpty () )
intersect = false;
else {
// 7000
float max_dist = 5000.0f;
tmp . orthoXform ( CamXpos ) ;
// cull if too far
if ( -tmp.center[2] - tmp.radius > max_dist )
intersect = false;
else if( tmp.center[2] == 0.0 )
intersect = true;
// cull if too small on screen
else if ( tmp.radius / sgAbs(tmp.center[2]) < 1.0 / 40.0 )
intersect = false;
else
intersect = SSG_OUTSIDE != (ssgCullResult) f -> contains ( &tmp );
}
if( intersect ) {
if( !scenery_object ) {
find_trans();
if( scenery_object )
traverseTree( pending_object );
else
return;
}
sgMat4 rotation, rotation_translation;
scenery_object->getNetTransform ( rotation_translation );
// split placement offset into rotation and offset
// rotation from model inside world
// we are not interested in translation since the light is very far away
// without translation we reduce the frequency of updates of the silouhette
sgCopyMat4( rotation, rotation_translation );
sgSetVec4( rotation[3], 0, 0, 0, 1);
ShadowCaster_list::iterator iShadowCaster;
for(iShadowCaster = parts.begin() ; iShadowCaster != parts.end() ; iShadowCaster ++ ) {
(*iShadowCaster)->computeShadows(rotation, rotation_translation);
}
}
}
/*
1) starting at the root of a scene object we follow all branch to find vertex leaf.
each vertex leaf will create a shadow sub object so that the shadow casted by this
geometry can be correctly rendered according to select or transform nodes.
2) models are not optimized for shadows because the geometry is cut by
- select / transform nodes
- material differences
since we are not interested in the geometry material we try to merge same level
leafs. this can divide the number of shadow sub models by a lot (reducing the cpu
overhead for matrix computation) and at the end this will reduce the number of
silouhette edge by a lot too.
*/
static bool filterName(const char *leaf_name) {
if( ! leaf_name )
return true;
char lname[20];
int l = 0;
char *buff;
for( buff = lname; *leaf_name && l < (sizeof( lname )-1); ++buff, l++ )
*buff = tolower(*leaf_name++);
*buff = 0;
if( strstr(lname, "shadow") || strstr(lname, "light") || strstr(lname, "disk") ||
strstr(lname, "disk") || strstr(lname, "flame") || strstr(lname, "halo"))
return false;
return true;
}
void SGShadowVolume::SceneryObject::traverseTree(ssgBranch *branch) {
int num_tri = 0;
int num_leaf = 0;
for(int i = 0 ; i < branch->getNumKids() ; i++) {
ssgEntity *this_kid = branch->getKid( i );
if( this_kid->isAKindOf(ssgTypeLeaf()) ) {
if( filterName( this_kid->getName()) ) {
num_tri += ((ssgLeaf *) this_kid)->getNumTriangles();
num_leaf ++;
}
} else
traverseTree( (ssgBranch *) this_kid );
}
if( num_tri > 0) {
int tri_idx = 0;
ShadowCaster *new_part = new ShadowCaster( num_tri, branch);
new_part->scenery_object = scenery_object;
new_part->lib_object = lib_object;
for(int i = 0 ; i < branch->getNumKids() ; i++) {
ssgEntity *this_kid = branch->getKid( i );
if( this_kid->isAKindOf(ssgTypeLeaf()) ) {
if( filterName( this_kid->getName()) )
new_part->addLeaf( tri_idx, (ssgLeaf *) this_kid );
}
}
new_part->SetConnectivity();
parts.push_back( new_part );
}
}
void SGShadowVolume::SceneryObject::find_trans(void) {
ssgBranch *branch = pending_object;
ssgBranch *prev_branch = pending_object;
// check the existence of the root node
while( branch && branch->getNumParents() > 0 ) {
prev_branch = branch;
branch = branch->getParent(0);
}
// check if we are connected to the scene graph
if( !branch->isA(ssgTypeRoot() ) )
return;
scenery_object = pending_object;
}
SGShadowVolume::SceneryObject::SceneryObject(ssgBranch *_scenery_object, OccluderType _occluder_type) :
pending_object ( _scenery_object ),
occluder_type ( _occluder_type ),
scenery_object ( 0 )
{
// queue objects, don't do all the work in the first frames because of
// massive cpu power needed
statObj++;
if( occluder_type == SGShadowVolume::occluderTypeAircraft )
lib_object = _scenery_object;
else
lib_object = (ssgBranch *) ((ssgBranch *)_scenery_object->getKid(0))->getKid(0);
#if 0
find_trans();
if( scenery_object )
traverseTree( pending_object );
#endif
}
SGShadowVolume::SceneryObject::~SceneryObject()
{
// parts.erase();
}
void SGShadowVolume::computeShadows(void) {
// intensity = ambient + lights
// lights = sun_const * (sun_angle . normal)
double dot_light = cos(sun_angle);
// do nothing if sun is under horizon
if( dot_light < 0.2 )
return;
//Draw shadow volumes
glPushAttrib(GL_ALL_ATTRIB_BITS);
glClear(GL_STENCIL_BUFFER_BIT);
glDisable( GL_LIGHTING );
glDisable( GL_FOG );
glDisable(GL_ALPHA);
glDisable(GL_BLEND);
glEnable( GL_CULL_FACE );
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glColorMask(0, 0, 0, 0);
glShadeModel(GL_FLAT);
glDepthMask( false );
glEnable( GL_DEPTH_TEST );
glDepthFunc(GL_LESS);
glEnable(GL_STENCIL_TEST);
SceneryObject_map::iterator iSceneryObject;
// compute shadows for each objects
for(iSceneryObject = sceneryObjects.begin() ; iSceneryObject != sceneryObjects.end(); iSceneryObject++ ) {
SceneryObject *an_occluder = iSceneryObject->second;
if( shadowsTO_enabled && (an_occluder->occluder_type == occluderTypeTileObject) ||
shadowsAI_enabled && (an_occluder->occluder_type == occluderTypeAI ) ||
shadowsAC_enabled && (an_occluder->occluder_type == occluderTypeAircraft ) )
an_occluder->computeShadows();
}
// now the stencil contains 0 for scenery in light and != 0 for parts in shadow
// draw a quad covering the screen, the stencil will be the mask
// we darken the shadowed parts
glStencilFunc(GL_NOTEQUAL, 0, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_STENCIL_TEST);
glColorMask(1, 1, 1, 1);
glMatrixMode ( GL_PROJECTION ) ;
glPushMatrix () ;
glLoadIdentity () ;
glOrtho ( -100, 100, -100, 100, -1, 1 ) ;
glMatrixMode ( GL_MODELVIEW ) ;
glPushMatrix () ;
glLoadIdentity () ;
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
// glBindTexture(GL_TEXTURE_2D, 0);
glPolygonMode(GL_FRONT, GL_FILL);
glEnable(GL_ALPHA);
glAlphaFunc(GL_GREATER, 0.0f);
glEnable(GL_BLEND);
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
glColor4f(0.0, 0.0, 0.0, sgLerp(0.1, 0.3, dot_light) );
// fixed value is better, the previous line is surely wrong
glColor4f(0.0, 0.0, 0.0, 0.3 );
// glColor4f(1.0, 0.0, 0.0, 0.5);
glRectf(-100,-100,100,100);
glMatrixMode ( GL_PROJECTION ) ;
glPopMatrix () ;
glMatrixMode ( GL_MODELVIEW ) ;
glPopMatrix () ;
glDisable(GL_STENCIL_TEST);
glPopAttrib();
}
SGShadowVolume::SGShadowVolume() :
init_done( false ),
shadows_enabled( false ),
frameNumber( 0 )
{
states = this;
}
SGShadowVolume::~SGShadowVolume() {
// sceneryObjects.erase();
}
void SGShadowVolume::init(SGPropertyNode *sim_rendering_options) {
init_done = true;
shadows_enabled = true;
sim_rendering = sim_rendering_options;
}
void SGShadowVolume::startOfFrame(void) {
}
void SGShadowVolume::deleteOccluderFromTile(ssgBranch *tile) {
SceneryObject_map::iterator iSceneryObject, iPrevious;
iPrevious = sceneryObjects.begin();
for(iSceneryObject = sceneryObjects.begin() ; iSceneryObject != sceneryObjects.end(); iSceneryObject++ ) {
if( iSceneryObject->second->tile == tile ) {
delete iSceneryObject->second;
sceneryObjects.erase( iSceneryObject );
iSceneryObject = iPrevious;
}
iPrevious = iSceneryObject;
}
}
void SGShadowVolume::deleteOccluder(ssgBranch *occluder) {
ssgBranch *branch = occluder;
// skip first node and go to first transform (placement)
while( occluder && !occluder->isA(ssgTypeTransform()))
occluder = (ssgBranch *) occluder->getKid(0);
// check if we allready know this object
SceneryObject_map::iterator iSceneryObject = sceneryObjects.find( occluder );
if( iSceneryObject != sceneryObjects.end() ) {
delete iSceneryObject->second;
sceneryObjects.erase( occluder );
}
}
void SGShadowVolume::addOccluder(ssgBranch *occluder, OccluderType occluder_type, ssgBranch *tile) {
ssgBranch *branch = occluder;
// skip first node and go to first transform (placement)
while( occluder && !occluder->isA(ssgTypeTransform()))
occluder = (ssgBranch *) occluder->getKid(0);
// check if we allready know this object
SceneryObject_map::iterator iSceneryObject = sceneryObjects.find( occluder );
if( iSceneryObject == sceneryObjects.end() ) {
// printf("adding model %x %x\n", occluder, tile);
SceneryObject *entry = new SceneryObject( occluder, occluder_type );
entry->tile = tile;
sceneryObjects[ occluder ] = entry;
}
}
void SGShadowVolume::setupShadows( double lon, double lat,
double gst, double SunRightAscension, double SunDeclination, double sunAngle) {
shadowsAC_enabled = sim_rendering->getBoolValue("shadows-ac", false);
shadowsAI_enabled = sim_rendering->getBoolValue("shadows-ai", false);
shadowsTO_enabled = sim_rendering->getBoolValue("shadows-to", false);
shadowsDebug_enabled = sim_rendering->getBoolValue("shadows-debug", false);
// shadows_enabled = sim_rendering->getBoolValue("shadows", false);
shadows_enabled = shadowsAC_enabled || shadowsAI_enabled || shadowsTO_enabled;
if( ! shadows_enabled )
return;
sgMat4 view_angle;
sun_angle = sunAngle;
{
sgMat4 LON, LAT;
sgVec3 axis;
sgSetVec3( axis, 0.0, 0.0, 1.0 );
sgMakeRotMat4( LON, lon, axis );
sgSetVec3( axis, 0.0, 1.0, 0.0 );
sgMakeRotMat4( LAT, 90.0 - lat, axis );
sgMat4 TRANSFORM;
sgMakeIdentMat4 ( TRANSFORM );
sgPreMultMat4( TRANSFORM, LON );
sgPreMultMat4( TRANSFORM, LAT );
sgCoord pos;
sgSetCoord( &pos, TRANSFORM );
sgMakeCoordMat4( view_angle, &pos );
}
{
sgMat4 GST, RA, DEC;
sgVec3 axis;
sgSetVec3( axis, 0.0, 0.0, -1.0 );
sgMakeRotMat4( GST, (gst) * 15.0, axis );
sgSetVec3( axis, 0.0, 0.0, 1.0 );
sgMakeRotMat4( RA, (SunRightAscension * SGD_RADIANS_TO_DEGREES) - 90.0, axis );
sgSetVec3( axis, 1.0, 0.0, 0.0 );
sgMakeRotMat4( DEC, SunDeclination * SGD_RADIANS_TO_DEGREES, axis );
sgInvertMat4( invViewAngle, view_angle);
sgMat4 TRANSFORM;
sgMakeIdentMat4( TRANSFORM );
sgPreMultMat4( TRANSFORM, GST );
sgPreMultMat4( TRANSFORM, RA );
sgPreMultMat4( TRANSFORM, DEC );
// sgSetVec3( sunPos, 0.0, 99000.0, 0.0);
sgSetVec3( sunPos, 0.0, 9900000.0, 0.0);
sgXformPnt3( sunPos, TRANSFORM );
}
ssgGetModelviewMatrix( CameraViewM );
}
void SGShadowVolume::endOfFrame(void) {
if( ! shadows_enabled )
return;
glBindTexture(GL_TEXTURE_2D, 0);
glBindTexture(GL_TEXTURE_1D, 0);
glMatrixMode(GL_MODELVIEW);
computeShadows();
frameNumber ++;
}
+142
View File
@@ -0,0 +1,142 @@
// Shadow volume class
//
// Written by Harald JOHNSEN, started June 2005.
//
// Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
//
//
#ifndef _SHADOWVOLUME_HXX
#define _SHADOWVOLUME_HXX
#include <plib/sg.h>
#include <vector>
#include <map>
SG_USING_STD(vector);
SG_USING_STD(map);
class ssgBranch;
class ssgLeaf;
class SGPropertyNode;
/**
* A class wich add shadows in a postprocess stage.
*/
class SGShadowVolume {
public:
SGShadowVolume();
~SGShadowVolume();
typedef enum {
occluderTypeAircraft,
occluderTypeAI,
occluderTypeTileObject
} OccluderType;
void init(SGPropertyNode *sim_rendering_options);
void startOfFrame(void);
void addOccluder(ssgBranch *occluder, OccluderType occluder_type, ssgBranch *tile=0);
void deleteOccluder(ssgBranch *occluder);
void deleteOccluderFromTile(ssgBranch *tile);
void setupShadows(double lon, double lat,
double gst, double SunRightAscension, double SunDeclination, double sunAngle );
void endOfFrame(void);
private:
class ShadowCaster
{
public:
ssgBranch *geometry_leaf;
ssgBranch *scenery_object;
ssgBranch *lib_object;
ssgBranch *first_select;
sgVec3 last_lightpos;
int frameNumber;
int *indices;
int numTriangles;
sgVec3 * vertices;
// plane equation of each face
sgVec4 * planeEquations;
bool * isFacingLight;
int * neighbourIndices;
bool * isSilhouetteEdge;
ShadowCaster( int _num_tri, ssgBranch * _geometry_leaf );
~ShadowCaster();
void addLeaf( int & tri_idx, ssgLeaf *_geometry_leaf );
void SetConnectivity();
void CalculateSilhouetteEdges(sgVec3 lightPosition);
void DrawInfiniteShadowVolume(sgVec3 lightPosition, bool drawCaps);
void computeShadows(sgMat4 rotation, sgMat4 rotation_translation);
void getNetTransform ( ssgBranch * branch, sgMat4 xform );
bool isSelected ( ssgBranch * branch );
bool sameVertex(int edge1, int edge2);
void addTri(int p, sgVec3 a, sgVec3 b, sgVec3 c);
};
typedef vector<ShadowCaster *> ShadowCaster_list;
class SceneryObject {
public:
SceneryObject(ssgBranch *_scenery_object, OccluderType _occluder_type);
~SceneryObject();
void computeShadows(void);
void traverseTree(ssgBranch *branch);
void find_trans(void);
ssgBranch *scenery_object;
ssgBranch *lib_object;
ssgBranch *pending_object;
ssgBranch *tile;
ShadowCaster_list parts;
OccluderType occluder_type;
};
typedef map<ssgBranch *, SceneryObject *> SceneryObject_map;
private:
void update_light_view(void);
void computeShadows(void);
bool init_done;
bool shadows_enabled;
bool shadowsAC_enabled, shadowsAI_enabled, shadowsTO_enabled, shadowsDebug_enabled;
SGPropertyNode *sim_rendering;
sgVec3 sunPos;
int frameNumber;
sgMat4 CameraViewM;
sgMat4 invViewAngle;
double sun_angle;
SceneryObject_map sceneryObjects;
/** this sphere contains the visible scene and is used to cull shadow casters */
sgSphere frustumSphere;
/** this sphere contains the near clip plane and is used to check the need of a zfail */
sgSphere nearClipSphere;
};
#endif // _SHADOWVOLUME_HXX