Ошибка удаления фрустума

Итак, я реализовал Frustum Culling в своем игровом движке, и у меня возникла странная ошибка. Я визуализирую здание, которое сегментировано на куски, и я визуализирую только те куски, которые находятся в усеченной пирамиде. Моя камера начинается примерно с (-033, 11,65, 2,2), и все выглядит нормально. Я начинаю двигаться, и мерцания нет. Когда я устанавливаю точку останова в коде отсечения усеченной пирамиды, я вижу, что он действительно отсеивает некоторые из мешей. Вроде все отлично. Затем, когда я добираюсь до центра здания, меши вокруг (3.9, 4.17, 2.23) начинают исчезать, которые находятся в поле зрения. То же верно и для другой стороны. Я не могу понять, почему эта ошибка могла существовать.

Я реализую отсечение усеченного конуса, используя перечисленный здесь метод извлечения Извлечение Просмотреть самолеты Frustum (метод Грибба и Хартмана). Мне пришлось использовать glm :: inverse () вместо того, чтобы транспонировать, как он предлагал, и я думаю, что математическая матрица была дана для матриц с старшими строками, поэтому я перевернул это. В целом мой расчет усеченного самолета выглядит как

std::vector<Mesh*> render_meshes;
auto comboMatrix = proj * glm::inverse(view * model);
glm::vec4 p_planes[6];

p_planes[0] = comboMatrix[3] + comboMatrix[0]; //left
p_planes[1] = comboMatrix[3] - comboMatrix[0]; //right
p_planes[2] = comboMatrix[3] + comboMatrix[1]; //bottom
p_planes[3] = comboMatrix[3] - comboMatrix[1]; //top
p_planes[4] = comboMatrix[3] + comboMatrix[2]; //near
p_planes[5] = comboMatrix[3] - comboMatrix[2]; //far

for (int i = 0; i < 6; i++){
    p_planes[i] = glm::normalize(p_planes[i]);
}
for (auto mesh : meshes) {
    if (!frustum_cull(mesh, p_planes)) {
        render_meshes.emplace_back(mesh);
    }
}

Затем я решаю отбраковать каждую сетку на основе ее ограничивающего прямоугольника (рассчитанного с помощью ASSIMP с флагом aiProcess_GenBoundingBoxes) следующим образом (возвращение истинного означает отбракованное)

glm::vec3 vmin, vmax;
for (int i = 0; i < 6; i++) {
    // X axis
    if (p_planes[i].x > 0) {
        vmin.x = m->getBBoxMin().x;
        vmax.x = m->getBBoxMax().x;
    }
    else {
        vmin.x = m->getBBoxMax().x;
        vmax.x = m->getBBoxMin().x;
    }
    // Y axis
    if (p_planes[i].y > 0) {
        vmin.y = m->getBBoxMin().y;
        vmax.y = m->getBBoxMax().y;
    }
    else {
        vmin.y = m->getBBoxMax().y;
        vmax.y = m->getBBoxMin().y;
    }
    // Z axis
    if (p_planes[i].z > 0) {
        vmin.z = m->getBBoxMin().z;
        vmax.z = m->getBBoxMax().z;
    }
    else {
        vmin.z = m->getBBoxMax().z;
        vmax.z = m->getBBoxMin().z;
    }
    if (glm::dot(glm::vec3(p_planes[i]), vmin) + p_planes[i][3] > 0)
        return true;
    
}
return false;

Любое руководство?

Обновление 1: нормализация полного вектора vec4, представляющего плоскость, неверна, поскольку только vec3 представляет нормаль к плоскости. Кроме того, в этом случае нормализация не требуется, поскольку нас интересует только знак расстояния (а не величина).

Также важно отметить, что я должен использовать строки матрицы, а не столбцы. Я добиваюсь этого, заменяя

p_planes[0] = comboMatrix[3] + comboMatrix[0];

с участием

p_planes[0] = glm::row(comboMatrix, 3) + glm::row(comboMatrix, 0);

во всех случаях.


person Alex Rowden    schedule 09.01.2021    source источник


Ответы (2)


Следуя решению @derhass для правильной нормализации плоскостей для тестов пересечения, вы должны сделать следующее

Для пересечения плоскости ограничивающего прямоугольника после проецирования вашего прямоугольника на плоскость, которую мы называем p, и после вычисления средней точки прямоугольника, скажем m, и после вычисления расстояния этой средней точки от плоскости скажем d, чтобы проверить пересечение, мы делаем

  d<=p

Но для отсечения усеченной вершины мы просто не хотим, чтобы наш блок НЕ пересекался с нашей плоскостью усеченной вершины, но мы хотим, чтобы он находился на расстоянии -p от нашей плоскости, и только тогда мы точно знаем, что НИКАКАЯ ЧАСТЬ нашего бокса не пересекает нашу плоскость, которая является

    if(d<=-p)//then our box is fully not intersecting our plane so we don't draw it or cull it[d will be negative if the midpoint lies on the other side of our plane]

Аналогично для треугольников мы проверяем, отрицательны ли ВСЕ 3 точки треугольника от плоскости.

Чтобы спроецировать прямоугольник на плоскость, мы берем 3 оси [x, y, z UNIT VECTORS] прямоугольника, масштабируем их по прямоугольникам, соответствующим ПОЛОВИНУ ширины, высоты, глубины и находим сумму каждого из их скалярных произведений [взять только положительная величина каждого скалярного произведения БЕЗ ПОДПИСАННОГО РАССТОЯНИЯ] с нормальными плоскостями, которые будут вашим 'p'

Не используя вышеупомянутый подход для AABB, вы также можете отбраковывать OOBB с тем же подходом, потому что изменятся только оси.

РЕДАКТИРОВАТЬ: как спроецировать ограничивающую рамку на плоскость?

Давайте рассмотрим AABB для нашего примера. Он имеет следующие параметры

Lower extent Min(x,y,z)
Upper extent Max(x,y,z)
Up Vector      U=(0,1,0)
Left Vector.   L=(1,0,0)
Front Vector.  F=(0,0,1)

Шаг 1: рассчитайте половинные размеры

half_width=(Max.x-Min.x)/2;
half_height=(Max.y-Min.y)/2;
half_depth=(Max.z-Min.z)/2;

Шаг 2: спроецируйте каждую отдельную ось прямоугольника на нормаль плоскости, возьмите только положительную величину каждого скалярного произведения, масштабированного на каждое половинное измерение, и найдите общую сумму. убедитесь, что и ось коробки, и нормаль к плоскости являются единичными векторами.

float p=(abs(dot(L,N))*half_width)+
        (abs(dot(U,N))*half_height)+
        (abs(dot(F,N))*half_depth);

 abs() returns absolute magnitude we want it to be positive 
 because we are dealing with distances

Где N - единичный вектор нормали плоскости

Шаг 3: вычислите среднюю точку бокса

 M=(Min+Max)/2;

Шаг 4: вычислить расстояние от средней точки до плоскости

d=dot(M,N)+plane.w

Шаг 5: сделайте проверку

d<=-p //return true i.e don't render or do culling

Вы можете увидеть, как использовать его для OOBB, где векторы U, F, L - это оси OOBB, а центр (средняя точка) и половинные размеры - это параметры, которые вы передаете вручную.

Для сферы вы также должны рассчитать расстояние центра сфер от плоскости (называемой d), но сделайте проверку

  d<=-r //radius of the sphere

Поместите это в функцию с именем outside (Plane, Bounds), которая возвращает истину, если границы полностью выходят за пределы плоскости, а затем для каждой из 6 плоскостей

   bool is_inside_frustum()
  {
    for(Plane plane:frustum_planes)
   {
      if(outside(plane,AABB))
     { 
       return false
     }
    }
    return true;
  } 
person Sync it    schedule 09.01.2021
comment
Извините, я не могу понять ваш прогноз. Вы говорите, что берете проекцию коробки, выполняя команду dot (plane_normal, axis_basis) * box_dimension_in_axis, а затем сравниваете ее с расстоянием от средней точки коробки до плоскости? - person Alex Rowden; 09.01.2021
comment
Да вы в принципе все поняли. Я отредактировал свой ответ, чтобы показать процедуру - person Sync it; 10.01.2021
comment
Это исправило! Я не знаю, следует ли принимать этот ответ в качестве ответа, потому что другой ответ также был необходим, чтобы сделать это правильно. Могу я получить совет по этому поводу у кого-нибудь более опытного? Должен ли я дать целостный ответ, резюмирующий оба момента? Оставить это без ответа? - person Alex Rowden; 11.01.2021
comment
@Alex Rowden предыдущий ответ просто касался проблем в вашем коде нормализации плоскости, который вы могли бы исправить самостоятельно, если бы ссылались на страницу 8 PDF-файла, но этот ответ, который не имеет ничего общего с плоскостями отсечения, а вместо этого с пересечением усеченной коробки / сетки что решило вашу проблему с частичным исчезновением предметов. Но опять же, это ваш вопрос, и вы являетесь сообществом, поэтому я позволю вам решить :) - person Sync it; 16.01.2021
comment
Это логический вывод, хотя я не хочу недооценивать вклад @ derhass, поскольку они также указали, что я использовал столбцы, когда мне следует использовать строки. На этом сайте много болтовни по поводу того, что правильно. Тем не менее я принял этот ответ. - person Alex Rowden; 18.01.2021

Вы неправильно используете GLM. Согласно статье Грибба и Хартманна, вы можете извлечь уравнения плоскости как сумма или разность различных строк матрицы, но в glm mat4 foo; foo[n] даст n-й столбец (аналогично тому, как разработан GLSL).

Это здесь

for (int i = 0; i < 6; i++){
    p_planes[i] = glm::normalize(p_planes[i]);
}

также не имеет смысла, поскольку glm::normalize(vec4) просто нормализует 4-мерный вектор. Это приведет к смещению плоскости в нормальном направлении. Только компоненты xyz должны быть доведены до единичной длины, а w должны быть соответственно масштабированы. Это даже подробно объясняется в самой статье. Однако, поскольку вам нужно только знать, на каком полупространстве лежит точка, нормализация уравнения плоскости - пустая трата циклов, вас волнует только знак, а не величина значения в любом случае.

person derhass    schedule 09.01.2021
comment
Итак, я пробовал это с glm :: row (foo, n), и я получаю еще худшие результаты. Теперь каждый меш отбирается (это тоже с удалением нормализации). - person Alex Rowden; 09.01.2021
comment
А не ту нормализацию убрали? - person derhass; 09.01.2021
comment
да. Нормализация удалена. - person Alex Rowden; 09.01.2021