OpenGL - Frustum не отбраковывает полигоны за пределами дальней плоскости

Я реализовал отсечение усеченной пирамиды и проверяю ограничивающую рамку на предмет пересечения с плоскостями усеченной вершины. Я добавил возможность приостанавливать обновление усеченной пирамиды, что позволяет мне видеть, правильно ли работает отсечение усеченной вершины. Когда я оборачиваюсь после того, как приостановил его, ничего не отображается позади меня, а также слева и справа, они сужаются так же хорошо, как и следовало ожидать. За пределами расстояния клипа (дальняя плоскость) они все еще визуализируются, и я не уверен, является ли это проблемой с моим обновлением усеченной вершины или кодом проверки ограничивающего прямоугольника, или я использую неправильную матрицу или что-то еще. Когда я поместил расстояние в матрицу проекции на 3000.0f, он все еще говорит, что ограничивающие прямоугольники далеко за пределами усеченной пирамиды, а это не так.

Вот где я создаю свою матрицу просмотра модели:

projectionMatrix = glm::perspective(newFOV, 4.0f / 3.0f, 0.1f, 3000.0f);

viewMatrix = glm::mat4(1.0);
viewMatrix = glm::scale(viewMatrix, glm::vec3(1.0, 1.0, -1.0));
viewMatrix = glm::rotate(viewMatrix, anglePitch, glm::vec3(1.0, 0.0, 0.0));
viewMatrix = glm::rotate(viewMatrix, angleYaw, glm::vec3(0.0, 1.0, 0.0));
viewMatrix = glm::translate(viewMatrix, glm::vec3(-x, -y, -z));

modelViewProjectiomMatrix = projectionMatrix * viewMatrix;

Причина, по которой я масштабирую его на -1 в направлении Z, заключается в том, что уровни были разработаны для рендеринга с помощью DirectX, поэтому я меняю направление Z на противоположное.

Вот где я обновляю свою усеченную пирамиду:

void CFrustum::calculateFrustum()
{
    glm::mat4 mat = camera.getModelViewProjectionMatrix();

    // Calculate the LEFT side
    m_Frustum[LEFT][A] = (mat[0][3]) + (mat[0][0]);
    m_Frustum[LEFT][B] = (mat[1][3]) + (mat[1][0]);
    m_Frustum[LEFT][C] = (mat[2][3]) + (mat[2][0]);
    m_Frustum[LEFT][D] = (mat[3][3]) + (mat[3][0]);

    // Calculate the RIGHT side
    m_Frustum[RIGHT][A] = (mat[0][3]) - (mat[0][0]);
    m_Frustum[RIGHT][B] = (mat[1][3]) - (mat[1][0]);
    m_Frustum[RIGHT][C] = (mat[2][3]) - (mat[2][0]);
    m_Frustum[RIGHT][D] = (mat[3][3]) - (mat[3][0]);

    // Calculate the TOP side
    m_Frustum[TOP][A] = (mat[0][3]) - (mat[0][1]);
    m_Frustum[TOP][B] = (mat[1][3]) - (mat[1][1]);
    m_Frustum[TOP][C] = (mat[2][3]) - (mat[2][1]);
    m_Frustum[TOP][D] = (mat[3][3]) - (mat[3][1]);

    // Calculate the BOTTOM side
    m_Frustum[BOTTOM][A] = (mat[0][3]) + (mat[0][1]);
    m_Frustum[BOTTOM][B] = (mat[1][3]) + (mat[1][1]);
    m_Frustum[BOTTOM][C] = (mat[2][3]) + (mat[2][1]);
    m_Frustum[BOTTOM][D] = (mat[3][3]) + (mat[3][1]);

    // Calculate the FRONT side
    m_Frustum[FRONT][A] = (mat[0][3]) + (mat[0][2]);
    m_Frustum[FRONT][B] = (mat[1][3]) + (mat[1][2]);
    m_Frustum[FRONT][C] = (mat[2][3]) + (mat[2][2]);
    m_Frustum[FRONT][D] = (mat[3][3]) + (mat[3][2]);

    // Calculate the BACK side
    m_Frustum[BACK][A] = (mat[0][3]) - (mat[0][2]);
    m_Frustum[BACK][B] = (mat[1][3]) - (mat[1][2]);
    m_Frustum[BACK][C] = (mat[2][3]) - (mat[2][2]);
    m_Frustum[BACK][D] = (mat[3][3]) - (mat[3][2]);

    // Normalize all the sides
    NormalizePlane(m_Frustum, LEFT);
    NormalizePlane(m_Frustum, RIGHT);
    NormalizePlane(m_Frustum, TOP);
    NormalizePlane(m_Frustum, BOTTOM);
    NormalizePlane(m_Frustum, FRONT);
    NormalizePlane(m_Frustum, BACK);
}

И, наконец, где я проверяю ограничивающую рамку:

bool CFrustum::BoxInFrustum( float x, float y, float z, float x2, float y2, float z2)
{
    // Go through all of the corners of the box and check then again each plane
    // in the frustum.  If all of them are behind one of the planes, then it most
    // like is not in the frustum.
    for(int i = 0; i < 6; i++ )
    {
        if(m_Frustum[i][A] * x  + m_Frustum[i][B] * y  + m_Frustum[i][C] * z  + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x2 + m_Frustum[i][B] * y  + m_Frustum[i][C] * z  + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x  + m_Frustum[i][B] * y2 + m_Frustum[i][C] * z  + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x2 + m_Frustum[i][B] * y2 + m_Frustum[i][C] * z  + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x  + m_Frustum[i][B] * y  + m_Frustum[i][C] * z2 + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x2 + m_Frustum[i][B] * y  + m_Frustum[i][C] * z2 + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x  + m_Frustum[i][B] * y2 + m_Frustum[i][C] * z2 + m_Frustum[i][D] > 0)  continue;
        if(m_Frustum[i][A] * x2 + m_Frustum[i][B] * y2 + m_Frustum[i][C] * z2 + m_Frustum[i][D] > 0)  continue;

        // If we get here, it isn't in the frustum
        return false;
    }

    // Return a true for the box being inside of the frustum
    return true;
}

person Pladnius Brooks    schedule 28.08.2012    source источник
comment
Обратите внимание, что вам не нужно тестировать каждый угол вашего AABB: вы можете выбрать один угол на основе нормали плоскости, которая, скорее всего, будет внутри (или снаружи, в зависимости от того, какой тип теста вы хотите)   -  person Ben Jackson    schedule 29.08.2012
comment
@BenJackson Хорошее замечание. Придется ли учитывать ротацию MVP?   -  person Pladnius Brooks    schedule 29.08.2012
comment
Вы пробовали используемые здесь методы? crownandcutlass.com/features/technicaldetails/frustum.html   -  person enderland    schedule 02.09.2012
comment
Куда вы инвертируете свою матрицу взглядов? Матрицу вида (модели камеры) нужно перевернуть.   -  person Michael IV    schedule 04.09.2012


Ответы (3)


Я заметил несколько вещей, особенно в том, как вы настраиваете матрицу проекции. Во-первых, gluProject не возвращает значение, если вы не используете какую-то оболочку или странный api. gluLookAt используется чаще.

Далее, предполагая, что функции масштабирования, поворота и смещения предназначены для изменения матрицы вида модели, вам необходимо изменить их порядок на обратный. OpenGL фактически не перемещает объекты; вместо этого он эффективно перемещает начало координат и отображает каждый объект, используя новое определение ‹0,0,0>. Таким образом, вы «перемещаетесь» туда, где хотите визуализировать, затем вращаете оси по мере необходимости, а затем растягиваете сетку.

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

Я вижу много незнакомого синтаксиса, поэтому я думаю, что вы используете какую-то оболочку; но вот некоторые (Qt) фрагменты кода из моего собственного проекта GL, который я использую. Может помочь, не знаю:

//This gets called during resize, as well as once during initialization
void GLWidget::resizeGL(int width, int height) {
  int side = qMin(width, height);
  padX = (width-side)/2.0;
  padY = (height-side)/2.0;
  glViewport(padX, padY, side, side);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, 1.0, 1.0, 2400.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

//This fragment gets called at the top of every paint event:
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glPushMatrix();

  glLightfv(GL_LIGHT0, GL_POSITION, FV0001);

  camMain.stepVars();

  gluLookAt(camMain.Pos[0],camMain.Pos[1],camMain.Pos[2],
            camMain.Aim[0],camMain.Aim[1],camMain.Aim[2],   
            0.0,1.0,0.0);

  glPolygonMode(GL_FRONT_AND_BACK, drawMode);

//And this fragment represents a typical draw event
void GLWidget::drawFleet(tFleet* tIn) {
  if (tIn->firstShip != 0){
    glPushMatrix();

    glTranslatef(tIn->Pos[0], tIn->Pos[1], tIn->Pos[2]);
    glRotatef(tIn->Yaw, 0.0, 1.0, 0.0);
    glRotatef(tIn->Pitch,0,0,1);

    drawShip(tIn->firstShip);

    glPopMatrix();
  }
}

Я исхожу из предположения, что вы новичок в GL, поэтому приношу свои извинения, если я показался немного педантичным.

person Ghost2    schedule 02.09.2012

У меня такая же проблема.

Учитывая ответ Винни Роуза, я проверил функцию, которая создает нормализованную плоскость, и обнаружил ошибку.

Это исправленная версия с закомментированными неверными вычислениями:

plane plane_normalized(float A, float B, float C, float D) {
    // Wrong, this is not a 4D vector
    // float nf = 1.0f / sqrtf(A * A + B * B + C * C + D * D);

    // Correct
    float nf = 1.0f / sqrtf(A * A + B * B + C * C);

    return (plane) {{
        nf * A,
        nf * B,
        nf * C,
        nf * D
    }};
}

Я предполагаю, что ваша функция NormalizePlane делает нечто подобное.

Смысл нормализации состоит в том, чтобы иметь плоскость в гессенской нормальной форме, чтобы мы могли легко выполнять полу- космические испытания. Если вы нормализуете плоскость, как четырехмерный вектор, направление нормали [A, B, C] по-прежнему будет правильным, а смещение D - нет.

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

Когда я восстановил правильную нормализацию, отбраковка усеченной кости у меня сработала, как и ожидалось.

person Justin    schedule 27.03.2014

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

Попробуйте другой подход: (http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-extracting-the-planes/)

float tang = tanf(fov * PI / 360.0f);
float nh = near * tang; // near height
float nw = nh * aspect; // near width
float fh = far * tang; // far height
float fw = fh * aspect; // far width

glm::vec3 p,nc,fc,X,Y,Z,Xnw,Ynh;

//camera position
p = glm::vec3(viewMatrix[3][0],viewMatrix[3][1],viewMatrix[3][2]);

// the left vector
glm::vec3 X = glm::vec3(viewMatrix[0][0], viewMatrix[1][0], viewMatrix[2][0]);
// the up vector
glm::vec3 Y = glm::vec3(viewMatrix[0][1], viewMatrix[1][1], viewMatrix[2][1]);
// the look vector
glm::vec3 Z = glm::vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]);

nc = p - Z * near; // center of the near plane
fc = p - Z * far; // center of the far plane

// the distance to get to the left or right edge of the near plane from nc
Xnw = X * nw;
// the distance to get to top or bottom of the near plane from nc
Ynh = Y * nh;
// the distance to get to the left or right edge of the far plane from fc
Xfw = X * fw;
// the distance to get to top or bottom of the far plane from fc
Yfh = Y * fh;

ntl = nc + Ynh - Xnw; // "near top left"
ntr = nc + Ynh + Xnw; // "near top right" and so on
nbl = nc - Ynh - Xnw;
nbr = nc - Ynh + Xnw;

ftl = fc + Yfh - Xfw;
ftr = fc + Yfh + Xfw;
fbl = fc - Yfh - Xfw;
fbr = fc - Yfh + Xfw;

m_Frustum[TOP] = planeWithPoints(ntr,ntl,ftl);
m_Frustum[BOTTOM] = planeWithPoints(nbl,nbr,fbr);
m_Frustum[LEFT] = planeWithPoints(ntl,nbl,fbl);
m_Frustum[RIGHT] = planeWithPoints(nbr,ntr,fbr);
m_Frustum[FRONT] = planeWithPoints(ntl,ntr,nbr);
m_Frustum[BACK] = planeWithPoints(ftr,ftl,fbl);

// Normalize all the sides
NormalizePlane(m_Frustum, LEFT);
NormalizePlane(m_Frustum, RIGHT);
NormalizePlane(m_Frustum, TOP);
NormalizePlane(m_Frustum, BOTTOM);
NormalizePlane(m_Frustum, FRONT);
NormalizePlane(m_Frustum, BACK);

Тогда planeWithPoints будет примерно таким:

planeWithPoints(glm::vec3 a, glm::vec3 b, glm::vec3 c){
    double A = a.y * (b.z - c.z) + b.y * (c.z - a.z) + c.y * (a.z - b.z);
    double B = a.z * (b.x - c.x) + b.z * (c.x - a.x) + c.z * (a.x - b.x);
    double C = a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y);
    double D = -(a.x * (b.y * c.z - c.y * b.z) + b.x * (c.y * a.z - a.y * c.z) + c.x * (a.y * b.z - b.y * a.z));
    return glm::vec4(A,B,C,D);
}

Я не тестировал ничего из вышеперечисленного. Но исходная ссылка есть, если вам это нужно.

Предыдущий ответ: Матрицы OpenGL и GLSL хранятся и доступны в порядке по столбцам, когда матрица представлена ​​двумерным массивом. То же самое и с GLM, поскольку они следуют стандартам GLSL.

Вам нужно изменить создание усеченной пирамиды на следующее.

// Calculate the LEFT side (column1 + column4)
m_Frustum[LEFT][A] = (mat[3][0]) + (mat[0][0]);
m_Frustum[LEFT][B] = (mat[3][1]) + (mat[0][1]);
m_Frustum[LEFT][C] = (mat[3][2]) + (mat[0][2]);
m_Frustum[LEFT][D] = (mat[3][3]) + (mat[0][3]);

// Calculate the RIGHT side (-column1 + column4)
m_Frustum[RIGHT][A] = (mat[3][0]) - (mat[0][0]);
m_Frustum[RIGHT][B] = (mat[3][1]) - (mat[0][1]);
m_Frustum[RIGHT][C] = (mat[3][2]) - (mat[0][2]);
m_Frustum[RIGHT][D] = (mat[3][3]) - (mat[0][3]);

// Calculate the TOP side (-column2 + column4)
m_Frustum[TOP][A] = (mat[3][0]) - (mat[1][0]);
m_Frustum[TOP][B] = (mat[3][1]) - (mat[1][1]);
m_Frustum[TOP][C] = (mat[3][2]) - (mat[1][2]);
m_Frustum[TOP][D] = (mat[3][3]) - (mat[1][3]);

// Calculate the BOTTOM side (column2 + column4)
m_Frustum[BOTTOM][A] = (mat[3][0]) + (mat[1][0]);
m_Frustum[BOTTOM][B] = (mat[3][1]) + (mat[1][1]);
m_Frustum[BOTTOM][C] = (mat[3][2]) + (mat[1][2]);
m_Frustum[BOTTOM][D] = (mat[3][3]) + (mat[1][3]);

// Calculate the FRONT side (column3 + column4)
m_Frustum[FRONT][A] = (mat[3][0]) + (mat[2][0]);
m_Frustum[FRONT][B] = (mat[3][1]) + (mat[2][1]);
m_Frustum[FRONT][C] = (mat[3][2]) + (mat[2][2]);
m_Frustum[FRONT][D] = (mat[3][3]) + (mat[2][3]);

// Calculate the BACK side (-column3 + column4)
m_Frustum[BACK][A] = (mat[3][0]) - (mat[2][0]);
m_Frustum[BACK][B] = (mat[3][1]) - (mat[2][1]);
m_Frustum[BACK][C] = (mat[3][2]) - (mat[2][2]);
m_Frustum[BACK][D] = (mat[3][3]) - (mat[2][3]);

person Vinny Rose    schedule 04.09.2012
comment
Кажется, это еще больше портит ситуацию. Усеченная пирамида работает, за исключением отбраковки предметов за дальнюю плоскость. Я предполагаю, что это либо ошибка вычисления дальней плоскости, либо что-то в том, как я вижу, проходят ли предметы за дальней плоскостью. - person Pladnius Brooks; 05.09.2012
comment
Да, ты прав. Я тестировал это только в одном случае, и оказалось, что это крайний случай. - person Vinny Rose; 06.09.2012
comment
Спасибо за обновление и альтернативный подход. Все еще работаем над тем, чтобы заставить его работать, поскольку некоторые из переменных не определены. Я ценю это. - person Pladnius Brooks; 09.09.2012