private GetFrustumClipVolumes ( |
||
camera | ||
return | PlaneBoundedVolumeList |
internal virtual PlaneBoundedVolumeList GetFrustumClipVolumes( Camera camera )
{
// Homogenous light position
Vector4 lightPos = this.GetAs4DVector();
// 3D version (not the same as DerivedPosition, is -direction for
// directional lights)
Vector3 lightPos3 = new Vector3( lightPos.x, lightPos.y, lightPos.z );
Vector3 lightDir;
Vector3[] clockwiseVerts = new Vector3[ 4 ];
Matrix4 eyeToWorld = camera.ViewMatrix.Inverse();
// Get worldspace frustum corners
Vector3[] corners = camera.WorldSpaceCorners;
bool infiniteViewDistance = ( camera.Far == 0 );
this.frustumClipVolumes.Clear();
for ( int n = 0; n < 6; n++ )
{
FrustumPlane frustumPlane = (FrustumPlane)n;
// skip far plane if infinite view frustum
if ( infiniteViewDistance && ( frustumPlane == FrustumPlane.Far ) )
{
continue;
}
Plane plane = camera[ frustumPlane ];
Vector4 planeVec = new Vector4( plane.Normal.x, plane.Normal.y, plane.Normal.z, plane.D );
// planes face inwards, we need to know if light is on negative side
float d = planeVec.Dot( lightPos );
if ( d < -1e-06f )
{
// Ok, this is a valid one
// clockwise verts mean we can cross-product and always get normals
// facing into the volume we create
this.frustumClipVolumes.Add( new PlaneBoundedVolume() );
PlaneBoundedVolume vol =
(PlaneBoundedVolume)this.frustumClipVolumes[ this.frustumClipVolumes.Count - 1 ];
switch ( frustumPlane )
{
case ( FrustumPlane.Near ):
clockwiseVerts[ 0 ] = corners[ 3 ];
clockwiseVerts[ 1 ] = corners[ 2 ];
clockwiseVerts[ 2 ] = corners[ 1 ];
clockwiseVerts[ 3 ] = corners[ 0 ];
break;
case ( FrustumPlane.Far ):
clockwiseVerts[ 0 ] = corners[ 7 ];
clockwiseVerts[ 1 ] = corners[ 6 ];
clockwiseVerts[ 2 ] = corners[ 5 ];
clockwiseVerts[ 3 ] = corners[ 4 ];
break;
case ( FrustumPlane.Left ):
clockwiseVerts[ 0 ] = corners[ 2 ];
clockwiseVerts[ 1 ] = corners[ 6 ];
clockwiseVerts[ 2 ] = corners[ 5 ];
clockwiseVerts[ 3 ] = corners[ 1 ];
break;
case ( FrustumPlane.Right ):
clockwiseVerts[ 0 ] = corners[ 7 ];
clockwiseVerts[ 1 ] = corners[ 3 ];
clockwiseVerts[ 2 ] = corners[ 0 ];
clockwiseVerts[ 3 ] = corners[ 4 ];
break;
case ( FrustumPlane.Top ):
clockwiseVerts[ 0 ] = corners[ 0 ];
clockwiseVerts[ 1 ] = corners[ 1 ];
clockwiseVerts[ 2 ] = corners[ 5 ];
clockwiseVerts[ 3 ] = corners[ 4 ];
break;
case ( FrustumPlane.Bottom ):
clockwiseVerts[ 0 ] = corners[ 7 ];
clockwiseVerts[ 1 ] = corners[ 6 ];
clockwiseVerts[ 2 ] = corners[ 2 ];
clockwiseVerts[ 3 ] = corners[ 3 ];
break;
}
// Build a volume
// Iterate over world points and form side planes
Vector3 normal;
for ( int i = 0; i < 4; i++ )
{
// Figure out light dir
lightDir = lightPos3 - ( clockwiseVerts[ i ] * lightPos.w );
// Cross with anticlockwise corner, therefore normal points in
// Note: C++ mod returns 3 for the first case where C# returns -1
int test = i > 0 ? ( ( i - 1 ) % 4 ) : 3;
// Cross with anticlockwise corner, therefore normal points in
normal = ( clockwiseVerts[ i ] - clockwiseVerts[ test ] ).Cross( lightDir );
normal.Normalize();
// NB last param to Plane constructor is negated because it's -d
vol.planes.Add( new Plane( normal, normal.Dot( clockwiseVerts[ i ] ) ) );
}
// Now do the near plane (this is the plane of the side we're
// talking about, with the normal inverted (d is already interpreted as -ve)
vol.planes.Add( new Plane( -plane.Normal, plane.D ) );
// Finally, for a point/spot light we can add a sixth plane
// This prevents false positives from behind the light
if ( this.type != LightType.Directional )
{
// re-use our own plane normal
// remember the -d negation in plane constructor
vol.planes.Add( new Plane( plane.Normal, plane.Normal.Dot( lightPos3 ) ) );
}
}
}
return this.frustumClipVolumes;
}
/// <summary> /// Internal method for locating a list of shadow casters which /// could be affecting the frustum for a given light. /// </summary> /// <remarks> /// Custom scene managers are encouraged to override this method to add optimizations, /// and to add their own custom shadow casters (perhaps for world geometry) /// </remarks> /// <param name="light"></param> /// <param name="camera"></param> protected virtual IList FindShadowCastersForLight( Light light, Camera camera ) { this.shadowCasterList.Clear(); if ( light.Type == LightType.Directional ) { // Basic AABB query encompassing the frustum and the extrusion of it AxisAlignedBox aabb = new AxisAlignedBox(); Vector3[] corners = camera.WorldSpaceCorners; Vector3 min, max; Vector3 extrude = light.DerivedDirection * -this.shadowDirLightExtrudeDist; // do first corner min = max = corners[ 0 ]; min.Floor( corners[ 0 ] + extrude ); max.Ceil( corners[ 0 ] + extrude ); for ( int c = 1; c < 8; ++c ) { min.Floor( corners[ c ] ); max.Ceil( corners[ c ] ); min.Floor( corners[ c ] + extrude ); max.Ceil( corners[ c ] + extrude ); } aabb.SetExtents( min, max ); if ( this.shadowCasterAABBQuery == null ) { this.shadowCasterAABBQuery = this.CreateAABBRegionQuery( aabb ); } else { this.shadowCasterAABBQuery.Box = aabb; } // Execute, use callback this.shadowCasterQueryListener.Prepare( false, light.GetFrustumClipVolumes( camera ), light, camera, this.shadowCasterList, light.ShadowFarDistanceSquared ); this.shadowCasterAABBQuery.Execute( this.shadowCasterQueryListener ); } else { Sphere s = new Sphere( light.DerivedPosition, light.AttenuationRange ); // eliminate early if camera cannot see light sphere if ( camera.IsObjectVisible( s ) ) { // create or init a sphere region query if ( this.shadowCasterSphereQuery == null ) { this.shadowCasterSphereQuery = this.CreateSphereRegionQuery( s ); } else { this.shadowCasterSphereQuery.Sphere = s; } // check if the light is within view of the camera bool lightInFrustum = camera.IsObjectVisible( light.DerivedPosition ); PlaneBoundedVolumeList volumeList = null; // Only worth building an external volume list if // light is outside the frustum if ( !lightInFrustum ) { volumeList = light.GetFrustumClipVolumes( camera ); } // prepare the query and execute using the callback this.shadowCasterQueryListener.Prepare( lightInFrustum, volumeList, light, camera, this.shadowCasterList, light.ShadowFarDistanceSquared ); this.shadowCasterSphereQuery.Execute( this.shadowCasterQueryListener ); } } return this.shadowCasterList; }