3D-Programmierung (Mathematik) Teil 11: Bounding-Sphären-Sichtbarkeitstest

Ein Schlüssel für die Performance eines Spiels besteht in der Durchführung von effizienten Sichtbarkeitstests mit dem Ziel, dass möglichst nur die sichtbaren Bereiche der Spielewelt gerendert werden.
Im denkbar einfachsten Test wird lediglich überprüft, ob sich ein Objekt vor der Kamera befindet:


void CGameObject::Check_Visibility(void)
{
    visible = false;

    PlayerViewDirectionDistance = ObjectScale +
                                  D3DXVec3Dot(&g_CameraViewDirection,
                                              &CameraSpacePosition);

    if(PlayerViewDirectionDistance > 0.0f)
        visible = true;
}

Befindet sich ein Objekt vor der Kamera und ist damit potenziell sichtbar, kann der Test in einem zweiten Schritt verfeinert werden, wobei man jedoch stets den Kosten-Nutzen-Effekt im Blick behalten muss – beansprucht der Sichtbarkeitstest mehr Rechenzeit als die eigentliche grafische Darstellung, dann ist ein genauerer Test kontraproduktiv und damit sinnlos.

Um die Anzahl der potenziell sichtbaren Objekte zusätzlich einzuschränken, kann man zudem eine maximale Reichweite (g_MaxObjectViewDistance) definieren, innerhalb derer ein Objekt noch wahrgenommen werden kann. Außerhalb dieses Bereichs ist das besagte Objekt bereits zu weit weg, um noch gesehen zu werden:


void CGameObject::Check_Visibility(void)
{
    visible = false;

    PlayerViewDirectionDistance = ObjectScale +
                                  D3DXVec3Dot(&g_CameraViewDirection,
                                              &CameraSpacePosition);

    if(PlayerViewDirectionDistance < 0.0f)
        return;

    if(PlayerViewDirectionDistance < g_MaxObjectViewDistance)
        visible = true;
}

Für 3D-Objekte mit hohem Polygon Count (mit zunehmender Anzahl der Dreiecksflächen steigt die benötigte Rendering-Zeit an) bzw. für eine größere Anzahl von Objekten in einem abgegrenzten Bereich der Spielewelt bietet sich die Verwendung des Bounding-Sphären-Sichtbarkeitstests an.
Im ersten Schritt definiert man zunächst eine Bounding-Sphäre, die zu testenden Objekte vollständig einschließt. Ob nun die zuvor definierte Sphäre innerhalb oder außerhalb des Blickfelds der Kamera liegt, berechnet sich gemäß der in der nachfolgenden Abbildung definierten Sichtbarkeitsbedingung:


















Hinweis:
Der Field-Of-View-Winkel (FOV-Winkel), der im Bounding-Sphären-Sichtbarkeitstest zum Einsatz kommt, entspricht nicht dem FOV-Winkel, der bei der Initialisierung der Projektionsmatrix verwendet wird, sondern berechnet sich wie folgt:

// halbes Blickfeld; wird für den Bounding-Sphären-Sichtbarkeitstest
// benötigt:
float tempFloat = 0.5f*g_FOV_Angle*D3DX_PI/180.0f;
g_HalfFOV = sqrtf((1.0f+g_Aspect)*tempFloat*tempFloat);

Kommen wir nun zur praktischen Implementierung des Bounding-Sphären-Sichtbarkeitstests:



void CGameObject::Check_Visibility(void)
{
    visible = false;

    PlayerViewDirectionDistance = ObjectScale +
                                  D3DXVec3Dot(&g_CameraViewDirection,
                                              &CameraSpacePosition);

    if(PlayerViewDirectionDistance < 0.0f)
        return;

    if(PlayerViewDirectionDistance < g_MaxObjectViewDistance)
    {
        // Bounding-Sphären-Sichtbarkeitstest:

        float CameraDistance = D3DXVec3LengthSq(&CameraSpacePosition);
        CameraDistance = sqrtf(CameraDistance);

        float InvCameraDistance = 1.0f/CameraDistance;

        float SphereAngle = ObjectScale*InvCameraDistance;
        SphereAngle = AsinValue(SphereAngle);

        float ViewAngle = AcosValue(PlayerViewDirectionDistance*
                                    InvCameraDistance);

        if(ViewAngle-SphereAngle < g_HalfFOV ||
           CameraDistance < ObjectScale)
            visible = true;
    }
}


verwendete Hilfsfunktionen:

INLINE float AsinValue(float v)
{
    if(v < -1.0f)
        return 0.0f;

    if(v > 1.0f)
        return 0.0f;

    return asinf(v);
}

INLINE float AcosValue(float v)
{
    if(v < -1.0f)
        return 0.0f;

    if(v > 1.0f)
        return 0.0f;

    return acosf(v);
}