netlib.narod.ru< Назад | Оглавление | Далее >

Знакомство с пирамидой видимого пространства

Пирамида видимого пространства (viewing frustum) — это набор из шести плоскостей, расходящихся от точки местоположения зрителя и определяющих, какие полигоны видимы, а какие — нет. Пирамида видимого пространства очень полезна для оптимизации обработки графики, и я хочу познакомить вас со всеми относящимися к ней запутанными деталями.

Вы можете думать о пирамиде видимого пространства, как о расширяющейся в направлении от вас пирамиде, в вершине которой находитесь вы (как показано на рис. 8.2). Эта пирамида представляет ваше поле зрения (field of view, или FOV). Все, что находится внутри пирамиды вы видите, а все, что снаружи, — нет.


Рис. 8.2. Пирамида видимого пространства обычно имеет форму усеченной пирамиды

Рис. 8.2. Пирамида видимого пространства обычно имеет форму усеченной пирамиды. Зритель видит все, что находится внутри нее


Если вы недоумеваете, как пирамида видимого пространства может помочь вам в трехмерном движке, примите во внимание следующее: все в вашем трехмерном графическом движке состоит из точек в трехмерном пространстве (называемых вершины). У пирамиды видимого пространства есть шесть сторон: передняя, задняя, левая, правая, верхняя и нижняя). Используя ряд математических вычислений можно определить, какие вершины находятся внутри пирамиды, а какие — вне ее. Вершины внутри пирамиды визуализируются, а вершины, находящиеся вне ее, — нет. То же самое справедливо и при визуализации полигонов — визуализируются только те вершины или грани, которые расположены внутри пирамиды видимого пространства.

Плоскости и отсечение

Шесть сторон пирамиды видимого пространства называются плоскостями отсечения (clipping planes). Для простоты думайте о плоскости как о бесконечно большом листе бумаги (с лицевой и обратной сторонами). Плоскость определяется четырьмя числами, которые обычно называют A, B, C и D. Эти четыре числа определяют ориентацию плоскости в трехмерном пространстве.

ПРИМЕЧАНИЕ
Direct3D, точнее D3DX, использует для хранения данных плоскости специальный объект D3DXPLANE. Он содержит четыре переменные: a, b, c и d — все типа float.

В главе 2 я упоминал нормали — числа, определяющие куда направлен объект. Обычно используемые для освещения, нормали могут применяться для описания ориентации чего угодно, и в нашем случае это направление лицевой стороны плоскости.

Вы определяете плоскость задавая ее ориентацию и перемещая на требуемое расстояние от начала координат. (В действительности плоскость определяется точно так же, как нормаль, но с дополнительным указанием расстояния от начала координат.) Взгляните на рис. 8.3, где показана ориентированная плоскость в трехмерном пространстве.


Рис. 8.3. Описание плоскости включает направление ее лицевой стороны и расстояние до начала координат

Рис. 8.3. Описание плоскости включает направление ее лицевой стороны и расстояние до начала координат


Вместо того, чтобы задавать значения нормали плоскости как X, Y, Z, вы используете переменные A, B и C. Требуется еще одно дополнительное значение для задания расстояния от плоскости до начала координат. Это расстояние представляется как D. Описывая плоскость вы устанавливаете в переменных A, B и C значения нормали плоскости, а в D — расстояние от плоскости до начала координат. Как только нормаль и расстояние заданы, вы можете использовать плоскость для того, чтобы проверить, находится ли указанная точка перед плоскостью или за ней.

Для вычисления шести плоскостей пирамиды видимого пространства вы комбинируете текущую матрицу преобразования вида и матрицу проекции. Затем вы имеете дело непосредственно с комбинированной матрицей для вычисления значений A, B, C и D каждой плоскости.

Вот как комбинируются две требуемые матрицы и на их основании вычисляются значения плоскостей (значения плоскостей помещаются в соответствующие объекты D3DXPLANE):

// Graphics = ранее инициализированный объект cGraphics
D3DXPLANE Planes[6];                 // Шесть плоскостей
                                     // пирамиды видимого пространства
D3DXMATRIX Matrix, matView, matProj; // Рабочие матрицы

// Получаем матрицы вида и проекции и комбинируем их
Graphics.GetDeviceCOM()->GetTransform(D3DTS_PROJECTION, &matProj);
Graphics.GetDeviceCOM()->GetTransform(D3DTS_VIEW, &matView);
D3DXMatrixMultiply(&Matrix, &matView, &matProj);

// Вычисляем плоскости
Planes[0].a = Matrix._14 + Matrix._13; // Передняя плоскость
Planes[0].b = Matrix._24 + Matrix._23;
Planes[0].c = Matrix._34 + Matrix._33;
Planes[0].d = Matrix._44 + Matrix._43;
D3DXPlaneNormalize(&Planes[0], &Planes[0]);

Planes[1].a = Matrix._14 - Matrix._13; // Задняя плоскость
Planes[1].b = Matrix._24 - Matrix._23;
Planes[1].c = Matrix._34 - Matrix._33;
Planes[1].d = Matrix._44 - Matrix._43;
D3DXPlaneNormalize(&Planes[1], &Planes[1]);

Planes[2].a = Matrix._14 + Matrix._11; // Левая плоскость
Planes[2].b = Matrix._24 + Matrix._21;
Planes[2].c = Matrix._34 + Matrix._31;
Planes[2].d = Matrix._44 + Matrix._41;
D3DXPlaneNormalize(&Planes[2], &Planes[2]);

Planes[3].a = Matrix._14 - Matrix._11; // Правая плоскость
Planes[3].b = Matrix._24 - Matrix._21;
Planes[3].c = Matrix._34 - Matrix._31;
Planes[3].d = Matrix._44 - Matrix._41;
D3DXPlaneNormalize(&Planes[3], &Planes[3]);.

Planes[4].a = Matrix._14 - Matrix._12; // Верхняя плоскость
Planes[4].b = Matrix._24 - Matrix._22;
Planes[4].c = Matrix._34 - Matrix._32;
Planes[4].d = Matrix._44 - Matrix._42;
D3DXPlaneNormalize(&Planes[4], &Planes[4]);

Planes[5].a = Matrix._14 + Matrix._12; // Нижняя плоскость
Planes[5].b = Matrix._24 + Matrix._22;
Planes[5].c = Matrix._34 + Matrix._32;
Planes[5].d = Matrix._44 + Matrix._42;
D3DXPlaneNormalize(&Planes[5], &Planes[5]);

 

ПРИМЕЧАНИЕ
Обратите внимание, что каждая плоскость нормализуется (в данном случае с использованием функции D3DXPlaneNormalize), чтобы гарантировать, что значения a, b, c будут меньше или равны 1.0f, а значение d будет хранить расстояние от плоскости до начала координат.

Проверка видимости с плоскостями

Теперь у вас есть плоскость (или набор плоскостей), ориентированная в заданном направлении. Чтобы проверить, находится ли точка перед плоскостью или за ней, вы вычисляете скалярное произведение (dot product). Скалярное произведение — это специальная операция с векторами (координатами), обычно применяемая для вычисления угла между двумя векторами.

При проверке местоположения точки относительно плоскости скалярное произведение сообщает вам расстояние от точки до плоскости. Если значение положительно, точка находится перед плоскостью. Если значение отрицательно — точка находится за плоскостью.

Для вычисления скалярного произведения вы используете функцию D3DXPlaneDotCoord:

FLOAT D3DXPlaneDotCoord(
     CONST D3DXPLANE   *pP,  // D3DXPLANE для проверки
     CONST D3DXVECTOR3 *pV); // Проверяемая точка

Функции D3DXPlaneDotCoord вы предоставляете структуру плоскости (содержащую значения плоскости) и точку (вектор, содержащийся в объекте D3DXVECTOR3). Проверка определит с какой стороны плоскости находится точка — спереди или сзади. После возвращения из функции D3DXPlaneDotCoord вы получаете расстояние от точки до плоскости. Это значение может быть равно нулю, положительным или отрицательным.

Если возвращенное значение равно 0, точка лежит на плоскости. Если значение отрицательно, то точка находится за плоскостью; если положительно — перед ней. Вот пример проверки:

// Plane = ранее инициализированный объект D3DXPLANE
// XPos, YPos, ZPos = проверяемая точка
float Dist = D3DXPlaneDotCoord(&Plane,
                               &D3DXVECTOR3(XPos, YPos, ZPos));

// Если dist > 0 точка перед плоскостью
// Если dist < 0 точка за плоскостью
// Если dist == 0 точка на плоскости

Проверка всей пирамиды

Хотя проверка отдельной точки полезна, вы можете пойти дальше и проверять целые объекты на нахождение внутри пирамиды видимого пространства. Проверяемыми объектами могут быть кубы, параллелепипеды и сферы.

Для кубов и параллелепипедов вы проверяете угловые вершины. Если все вершины находятся за плоскостью, значит куб или параллелепипед вне пирамиды видимого пространства (и вне поля зрения). Если хоть одна вершина внутри пирамиды, или перед любой плоскостью (не все вершины расположены позади одной плоскости), означает, что куб или параллелепипед видимы. Что касается сферы, то чтобы она была видима расстояние от каждой плоскости до центра сферы должно быть равно или больше радиуса.

Чтобы проверить произвольное количество точек, вы проверяете каждую из них индивидуально, убеждаясь, что хотя бы одна находится перед всеми плоскостями. В следующем разделе вы узнаете как создать класс, конструирующий пирамиду видимого пространства и проверяющий видимость объектов с использованием этой пирамиды.

Класс cFrustum

Теперь вы познакомились с математикой, лежащей в основе использования пирамиды видимого пространства и, хотя эта математика относительно проста, желательно использовать пирамиду видимого пространства без постоянного повторного набора одного и того же кода. Поскольку математика при каждом использовании пирамиды видимого пространства остается одной и той же, имеет смысл создать класс, который будет выполнять для вас все вычисления, включая вычисление пирамиды видимого пространства и использование пирамиды для проверки видимости объектов.

Класс, о котором я говорю, — это cFrustum.

class cFrustum
{
  private:
    D3DXPLANE m_Planes[6]; // Плоскости пирамиды

  public:
    // Конструируем шесть плоскостей на основании
    // текущего вида и проекции. Можете переопределить
    // подставляемое по умолчанию значение глубины.
    BOOL Construct(cGraphics *Graphics, float ZDistance = 0.0f);

    // Последующие функции проверяют отдельную точку, куб,
    // параллелепипед и сферу на предмет нахождения внутри пирамиды.
    // Возвращаемое значение TRUE означает, что объект видим,
    // FALSE - что невидим. При проверке кубов и параллелепипедов
    // вы можете указать переменную BOOL, определяющую все ли
    // точки объекта находятся внутри пирамиды
    BOOL CheckPoint(float XPos, float YPos, float ZPos);
    BOOL CheckCube(float XCenter, float YCenter,
                   float ZCenter, float Size,
                   BOOL *CompletelyContained = NULL);
    BOOL CheckRectangle(float XCenter, float YCenter,
                        float ZCenter, float XSize,
                        float YSize, float ZSize,
                        BOOL *CompletelyContained = NULL);
    BOOL CheckSphere(float XCenter, float YCenter,
                     float ZCenter, float Radius);
};

Построенный только из пяти функций и массива объектов D3DPLANE, класс cFrustum предлагает вам много функциональных возможностей. Давайте взглянем, что можно делать с каждой из этих пяти функций.

cFrustum::Construct

Вы используете эту функцию, которую необходимо вызывать каждый раз после изменения матриц проекции или вида, для конструирования шести плоскостей проверки:

BOOL cFrustum::Construct(cGraphics *Graphics, float ZDistance)
{
    D3DXMATRIX Matrix, matView, matProj;
    float ZMin, Q;

    // Проверка ошибок
    if(Graphics == NULL || Graphics->GetDeviceCOM() == NULL)
        return FALSE;

    // Вычисление данных FOV
    Graphics->GetDeviceCOM()->GetTransform(D3DTS_PROJECTION,
                                           &matProj);

    if(ZDistance != 0.0f) {

        // Вычисление новой матрицы проекции
        // на основании заданной дистанции
        ZMin = -matProj._43 / matProj._33;
        Q = ZDistance / (ZDistance - ZMin);
        matProj._33 = Q;
        matProj._43 = -Q * ZMin;
    }
    Graphics->GetDeviceCOM()->GetTransform(D3DTS_VIEW, &matView);
    D3DXMatrixMultiply(&Matrix, &matView, &matProj);

    // Вычисление плоскостей
    m_Planes[0].a = Matrix._14 + Matrix._13; // Передняя плоскость
    m_Planes[0].b = Matrix._24 + Matrix._23;
    m_Planes[0].c = Matrix._34 + Matrix._33;
    m_Planes[0].d = Matrix._44 + Matrix._43;
    D3DXPlaneNormalize(&m_Planes[0], &m_Planes[0]);

    m_Planes[1].a = Matrix._14 - Matrix._13; // Задняя плоскость
    m_Planes[1].b = Matrix._24 - Matrix._23;
    m_Planes[1].c = Matrix._34 - Matrix._33;
    m_Planes[1].d = Matrix._44 - Matrix._43;
    D3DXPlaneNormalize(&m_Planes[1], &m_Planes[1]);

    m_Planes[2].a = Matrix._14 + Matrix._11; // Левая плоскость
    m_Planes[2].b = Matrix._24 + Matrix._21;
    m_Planes[2].c = Matrix._34 + Matrix._31;
    m_Planes[2].d = Matrix._44 + Matrix._41;
    D3DXPlaneNormalize(&m_Planes[2], &m_Planes[2]);

    m_Planes[3].a = Matrix._14 - Matrix._11; // Правая плоскость
    m_Planes[3].b = Matrix._24 - Matrix._21;
    m_Planes[3].c = Matrix._34 - Matrix._31;
    m_Planes[3].d = Matrix._44 - Matrix._41;
    D3DXPlaneNormalize(&m_Planes[3], &m_Planes[3]);

    m_Planes[4].a = Matrix._14 - Matrix._12; // Верхняя плоскость
    m_Planes[4].b = Matrix._24 - Matrix._22;
    m_Planes[4].c = Matrix._34 - Matrix._32;
    m_Planes[4].d = Matrix._44 - Matrix._42;
    D3DXPlaneNormalize(&m_Planes[4], &m_Planes[4]);

    m_Planes[5].a = Matrix._14 + Matrix._12; // Нижняя плоскость
    m_Planes[5].b = Matrix._24 + Matrix._22;
    m_Planes[5].c = Matrix._34 + Matrix._32;
    m_Planes[5].d = Matrix._44 + Matrix._42;
    D3DXPlaneNormalize(&m_Planes[5], &m_Planes[5]);

    return TRUE;
}

Большую часть этого кода вы уже видели в разделе «Знакомство с пирамидой видимого пространства» этой главы, но здесь добавлена одна вещь — возможность переопределять расстояние до дальней плоскости отсечения при формировании пирамиды видимого пространства. Если вы хотите, чтобы видимы были только близкие объекты, задайте новое расстояние до дальней плоскости отсечения. Чтобы вычислить новую плоскость отсечения необходимо заново вычислить значения матрицы для переднего и заднего планов, как показано в коде.

cFrustum::CheckPoint, CheckCube, CheckRectangle и CheckSphere

Вы используете эти четыре функции, чтобы определить видимо ли что-нибудь внутри пирамиды видимого пространства. Как видите, функции прямолинейны и самодокументируемы:

BOOL cFrustum::CheckPoint(float XPos, float YPos, float ZPos)
{
    // Убеждаемся, что точка внутри пирамиды
    for(short i = 0; i < 6; i++) {
        if(D3DXPlaneDotCoord(&m_Planes[i],
               &D3DXVECTOR3(XPos, YPos, ZPos)) < 0.0f)
            return FALSE;
    }
    return TRUE;
}

В показанной функции CheckPoint проверяется, что рассматриваемая точка находится перед всеми шестью плоскостями отсечения. Помните, что точка находится перед плоскостью, если скалярное произведение положительно. Если для какой-нибудь плоскости скалярное произведение отрицательно, функция возвращает FALSE, сообщая, что точка находится за плоскостью и, следовательно, вне поля зрения.

BOOL cFrustum::CheckCube(float XCenter, float YCenter,
                         float ZCenter, float Size,
                         BOOL *CompletelyContained)
{
    short i;
    DWORD TotalIn = 0;

    // Подсчитываем количество точек внутри пирамиды
    for(i = 0; i < 6; i++) {
        DWORD Count = 8;
        BOOL PointIn = TRUE;

        // Проверяем все восемь точек относительно плоскости
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter-Size,
                                          YCenter-Size,
                                          ZCenter-Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter+Size,
                                          YCenter-Size,
                                          ZCenter-Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter-Size,
                                          YCenter+Size,
                                          ZCenter-Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter+Size,
                                          YCenter+Size,
                                          ZCenter-Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter-Size,
                                          YCenter-Size,
                                          ZCenter+Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter+Size,
                                          YCenter-Size,
                                          ZCenter+Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter-Size,
                                          YCenter+Size,
                                          ZCenter+Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }
        if(D3DXPlaneDotCoord(&m_Planes[i],
                             &D3DXVECTOR3(XCenter+Size,
                                          YCenter+Size,
                                          ZCenter+Size)) < 0.0f) {
            PointIn = FALSE;
            Count--;
        }

        // Если внутри пирамиды ничего нет, возвращаем FALSE
        if(Count == 0)
            return FALSE;

        // Обновляем счетчик, если они все перед плоскостью
        TotalIn += (PointIn == TRUE) ? 1:0;
    }
    // Сохраняем флаг BOOL если проверка полного нахождения внутри
    if(CompletelyContained != NULL)
        *CompletelyContained = (TotalIn == 6) ? TRUE:FALSE;

    return TRUE;
}

Тем же способом, которым функция CheckPoint проверяет местоположение единственной точки относительно всех шести плоскостей пирамиды видимого пространства, функция CheckCube проверяет восемь вершин куба. Поскольку куб симметричен, вы задаете координаты центра и расстояние от центра до одного из краев (можете считать, что задаете радиус куба).

Следует отметить одну вещь относительно CheckCube (и следующей функции CheckRectangle) — она проверяет местоположение всех восьми точек относительно всех шести плоскостей. Если все точки находятся за одной из плоскостей, функция возвращает FALSE, сообщая, что куб полностью вне поля зрения. Если хотя бы одна точка находится внутри пирамиды видимого пространства или пирамида находится внутри куба (внутри пирамиды видимого пространства не содержится вершин, но как минимум одна вершина находится перед плоскостью), функция возвращает TRUE, сигнализируя, что куб видим в пирамиде.

Если все точки находятся перед всеми шестью плоскостями, куб полностью находится внутри пирамиды. Здесь в дело вступает переменная CompletelyContained типа BOOL; она устанавливается в TRUE, если все восемь точек находятся внутри пирамиды видимого пространства, в ином случае ей присваивается значение FALSE.

Функция CheckRectangle идентична функции CheckCube, за исключением того, что вы задаете ширину, высоту и глубину отдельно. Помните, что передаваемые функции значения должны быть в два раза меньше, чем реальные ширина, высота и глубина. Например, для параллелепипеда размерами 30 × 20 × 10 в вызове функции CheckRectangle вы указываете 15, 10 и 5.

Поскольку код во многом тот же самый, я пропущу его, и вместо этого приведу тоько прототип (полный код находится на прилагаемом к книге CD-ROM):

BOOL cFrustum::CheckRectangle(float XCenter, float YCenter,
                              float ZCenter, float XSize,
                              float YSize, float ZSize,
                              BOOL *CompletelyContained);

Переходим к следующему, и последнему объекту, видимость которого можно определять — сфере. Функция CheckSphere слегка отличается от остальных — она получает только координаты центра и радиус сферы:

BOOL cFrustum::CheckSphere(float XCenter, float YCenter,
                           float ZCenter, float Radius)
{
    short i;

    // Убеждаемся, что радиус внутри пирамиды
    for(i = 0; i < 6; i++) {
        if(D3DXPlaneDotCoord(&m_Planes[i],
                 &D3DXVECTOR3(XCenter, YCenter, ZCenter)) < -Radius)
            return FALSE;
    }
    return TRUE;
}

Показанная функция CheckSphere, пожалуй, самая простая функция проверки пирамиды видимого пространства. Если сфера находится перед плоскостью (или касается ее), расстояние до центра будет больше, чем отрицательный радиус. Если расстояние меньше отрицательного радиуса, сфера находится вне поля зрения.

Как видите, у класса cFrustum много применений, включая разработку профессионального трехмерного движка, который можно использовать для рисования уровней, что и будет нашей следующей темой.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz