Подтвердить что ты не робот

Вычисление нормалей в треугольной сетке

Я нарисовал треугольную сетку с 10000 вершинами (100x100), и это будет трава. Для этого я использовал gldrawelements(). Я весь день смотрел и до сих пор не могу понять, как рассчитать нормали для этого. Каждая вершина имеет свои собственные нормали или каждый треугольник имеет свои собственные нормали? Может ли кто-нибудь указать мне в правильном направлении, как отредактировать мой код, чтобы включить нормали?

struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[60000];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=0;
            vertices[count].z=z;
            count++;
        }
    }
    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glEnableClientState(GL_VERTEX_ARRAY);
    glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glPopMatrix();
}

ИЗМЕНИТЬ 1 Вот код, который я написал. Я просто использовал массивы вместо векторов, и я сохранил все нормали в структуре, называемые нормалями. Однако он все еще не работает. Я получаю необработанное исключение по индексам *.

struct Normals {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}normals[20000];
Normals* normal = normals;
//***************************************ENVIRONMENT*************************************************************************
struct vertices {
    GLfloat x;
    GLfloat y;
    GLfloat z;
}vertices[10000];

GLuint indices[59403];

/*
99..9999
98..9998
........
01..9901
00..9900
*/

void CreateEnvironment() {
    int count=0;
    for (float x=0;x<10.0;x+=.1) {
        for (float z=0;z<10.0;z+=.1) {
            vertices[count].x=x;
            vertices[count].y=rand()%2-2;;
            vertices[count].z=z;
            count++;
        }
    }
    //calculate normals 
    GLfloat vector1[3];//XYZ
    GLfloat vector2[3];//XYZ
    count=0;
    for (int x=0;x<9900;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }
    count=10000;
    for (int x=100;x<10000;x+=100){
        for (int z=0;z<99;z++){
            vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS
            vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
            vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
            vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x
            vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y
            vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z
            normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
            normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
            normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
        }
    }

    count=0;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            GLuint v1=(a*100)+b;indices[count]=v1;count++;
            GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
            GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
        }
    }
    count=30000;
    for (GLuint a=0;a<99;a++){
        for (GLuint b=0;b<99;b++){
            indices[count]=(a*100)+b+100;count++;//9998
            indices[count]=(a*100)+b+1;count++;//9899
            indices[count]=(a*100)+b+101;count++;//9999
        }
    }
}

void ShowEnvironment(){
    //ground
    glPushMatrix();
    GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
    GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0};
    glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
    glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer( GL_FLOAT, 0, normal);
    glVertexPointer(3,GL_FLOAT,0,vertices);
    glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glPopMatrix();
}
//***************************************************************************************************************************
4b9b3361

Ответ 1

Каждая вершина имеет собственные нормали или каждый треугольник имеет свои собственные нормали?

Как часто, ответ: "Это зависит". Так как нормаль определяется как вектор, перпендикулярный всем векторам в данной плоскости (в N измерениях), вам нужна плоскость для вычисления нормали. Вершинная позиция является просто точкой и, следовательно, единственной, поэтому вам действительно нужно лицо, чтобы вычислить нормальное. Таким образом, наивно можно было бы предположить, что нормали на лице, поскольку первым шагом в нормальном вычислении является определение нормалей лица, путем вычисления поперечного произведения граней граней.

Скажем, у вас есть треугольник с точками A, B, C, тогда эти точки имеют позиционные векторы ↑ A, ↑ B, ↑ C, а ребра имеют векторы ↑ B - ↑ A и ↑ C - ↑ A так что нормальный вектор лица ↑ N f= (↑ B - ↑ A) × (↑ C - ↑ A)

Обратите внимание, что величина ↑ N f, как указано выше, прямо пропорциональна площади лица.

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

↑ N v= Σ p ↑ N f; где p - взвешивание для каждой грани.

Можно было либо принять равное взвешивание между участвующими нормалями лица. Но имеет смысл предположить, что чем больше лицо, тем больше оно способствует нормальному.

Теперь напомним, что вы нормализуетесь с помощью вектора ↑ v, масштабируя его с помощью рекурсивной длины: ↑ v i= ↑ v/| ↑ v |. Но, как уже говорилось, длина лица нормали уже зависит от площади лица. Таким образом, приведенный выше весовой коэффициент p уже содержится в самом векторе: его длина, aka величина. Таким образом, мы можем получить нормальный вектор вершины, просто суммируя все нормали лица.

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

Внимательный читатель, возможно, заметил, что я специально сказал, что гладкие поверхности разделяют вершины. И на самом деле, если у вас есть какие-то складки/жесткие края в вашей геометрии, то грани с обеих сторон не разделяют вершины. В OpenGL вершина представляет собой целую комбинацию

  • Позиция
  • нормально
  • (цвет)
  • N текстурных координат
  • M дополнительных атрибутов

Вы меняете один из них, и у вас есть совершенно другая вершина. Теперь некоторые 3D-моделисты видят вершину только как точку и сохраняют остальные атрибуты на лицо (Blender - такой модельер). Это экономит память (или значительную память, в зависимости от количества атрибутов). Но OpenGL все это нужно, поэтому, если вы работаете с таким смешанным парадигменным файлом, вам сначала придется разложить его на совместимые с OpenGL данные. Посмотрите один из сценариев экспорта Blender, например, экспортер PLY, чтобы посмотреть, как это делается.


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

 glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );

Указатель указателей имеет ничего для индексов массива вершин! Это анахроним от дней, когда графика по-прежнему использовала палитры вместо истинного цвета. Цвет пикселей не был задан, задав его значения RGB, но одним смещением номера в ограниченную палитру цветов. Цвета палитры все еще можно найти в нескольких форматах графических файлов, но никакая порядочная аппаратная часть их больше не использует.

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

Ответ 2

Per-вершины.

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

Ответ 3

Незнакомец! Я полностью согласен с его подходом. Добавление нормальных векторов смежных треугольников для каждой вершины, а затем нормализация - путь. Я просто хочу немного подтолкнуть ответ и поближе рассмотреть конкретный, но довольно распространенный случай прямоугольной, гладкой сетки с константой x/y шаг. Другими словами, прямоугольная сетка x/y с переменной высотой в каждой точке.

Такая сетка создается путем циклирования по х и у и установки значения для z и может представлять такие вещи, как поверхность холма. Поэтому каждая точка сетки представлена ​​вектором

P = (x, y, f(x,y)) 

где f (x, y) - функция, задающая z каждой точки на сетке.

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

     |/   |/   |/   |/
...--+----U----UR---+--...
    /|   /| 2 /|   /|           Y
   / |  / |  / |  / |           ^
     | /  | /  | /  | /         |
     |/ 1 |/ 3 |/   |/          |
...--L----P----R----+--...      +-----> X
    /| 6 /| 4 /|   /|          
   / |  / |  / |  / |         
     | /5 | /  | /  | /      
     |/   |/   |/   |/
...--DL---D----+----+--...
    /|   /|   /|   /|

Для треугольникаStrip каждая вершина P = (x0, y0, z0) имеет 6 смежных вершин, обозначенных

up       = (x0     , y0 + ay, Zup)
upright  = (x0 + ax, y0 + ay, Zupright) 
right    = (x0 + ax, y0     , Zright) 
down     = (x0     , y0 - ay, Zdown)
downleft = (x0 - ax, y0 - ay, Zdownleft) 
left     = (x0 - ax, y0     , Zleft)

где ax/ay - шаг постоянной сетки на оси x/y соответственно. На квадратной сетке ax = ay.

ax = width / (nColumns - 1)
ay = height / (nRows - 1)

Таким образом, каждая вершина имеет 6 смежных треугольников, каждый из которых имеет свой собственный нормальный вектор (обозначается N1-N6). Они могут быть рассчитаны с использованием поперечного произведения двух векторов, определяющих сторону треугольника, и быть осторожным в том порядке, в котором мы выполняем кросс-произведение. Если нормальный вектор указывает в направлении Z к вам:

N1 = up x left =
   = (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup) 

   =( (y0 + ay)*Zleft - y0*Zup, 
      (x0 - ax)*Zup   - x0*Zleft, 
      x0*y0 - (y0 + ay)*(x0 - ax) ) 

N2 = upright  x up
N3 = right    x upright
N4 = down     x right
N5 = downleft x down
N6 = left     x downleft

И получившийся нормальный вектор для каждой точки P является суммой от N1 до N6. Мы нормируем после суммирования. Очень легко создать цикл, вычислить значения каждого нормального вектора, добавить их и затем нормализовать. Однако, как отметил г-н Шикаданс, это может занять довольно много времени, особенно для больших ячеек и/или встроенных устройств.

Если мы поближе рассмотрим и проведем вычисления вручную, мы выясним, что большинство терминов отменяют друг друга, оставляя нам очень элегантное и простое вычисление окончательного решения для результирующего вектора N. Точка здесь нужно ускорить вычисления, избегая вычисления координат от N1 до N6, делая 6 кросс-продуктов и 6 дополнений для каждой точки. Алгебра помогает нам перейти прямо к решению, использовать меньше памяти и меньше процессорного времени.

Я не буду показывать детали вычислений, поскольку он длинный, но прямолинейный и перейдет к окончательному выражению Нормального вектора для любой точки сетки. Только N1 разлагается для ясности, другие векторы выглядят одинаково. После суммирования получаем N, который еще не нормирован:

N = N1 + N2 + ... + N6

  = .... (long but easy algebra) ...

  = ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax,
      (2*(Zdown - Zup)    + Zupright + Zdownleft - Zup - Zleft) / ay,
       6 )

Иди сюда! Просто нормализуйте этот вектор, и у вас есть нормальный вектор для любой точки сетки, если вы знаете значения Z его окружающих точек и горизонтальный/вертикальный шаг вашей сетки.

Заметим, что это взвешенное среднее нормальных векторов окружающих треугольников. Вес - это площадь треугольников и уже включена в кросс-продукт.

Вы можете даже упростить его, только принимая во внимание значения Z четырех соседних точек (вверх, вниз, влево и вправо). В этом случае вы получите:

                                             |   \|/   |
N = N1 + N2 + N3 + N4                    ..--+----U----+--..
  = ( (Zleft - Zright) / ax,                 |   /|\   |
      (Zdown -  Zup  ) / ay,                 |  / | \  |
       2 )                                 \ | / 1|2 \ | /
                                            \|/   |   \|/
                                         ..--L----P----R--...
                                            /|\   |   /|\
                                           / | \ 4|3 / | \
                                             |  \ | /  |
                                             |   \|/   |
                                         ..--+----D----+--..
                                             |   /|\   |

который еще более изящен и даже быстрее вычисляется.

Надеюсь, что это сделает некоторые сетки быстрее. Приветствия

Ответ 4

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

Так почему нормализованный перекрестный продукт является лишь частью проблемы? Порядок намотки вершин в этом многоangularьнике определяет направление нормали, т.е. если одна пара вершин поменяется местами, нормаль будет указывать в противоположном направлении. Таким образом, на самом деле это может быть проблематично, если сама сетка содержит несоответствия в этом отношении, то есть ее части предполагают одно упорядочение, в то время как другие части предполагают разное упорядочение. Один известный пример - оригинальная модель Stanford Bunny, где некоторые части поверхности будут направлены внутрь, а другие - наружу. Причина этого заключается в том, что модель была построена с использованием сканера, и не было предпринято никаких усилий для получения треangularьников с регулярными рисунками намотки. (очевидно, чистые версии кролика также существуют)

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

В этом же смысле отключенные многоangularьные супы и облака точек создают проблемы для точной реконструкции из-за плохо определенного числа обмоток.

Одна потенциальная стратегия, которая часто используется для решения этой проблемы, состоит в том, чтобы стрелять случайными лучами от центра к каждой полутриангуляции (т.е. лучевой удар). Но нельзя предполагать, что триангуляция верна, если многоangularьники могут содержать несколько вершин, поэтому лучи могут пропустить этот конкретный подтреangularьник. Если луч попадает, то нормаль противоположна направлению луча, то есть с точкой (луч, n) & lt;.5 удовлетворено, может использоваться как нормальное для всего многоangularьника. Очевидно, что это довольно дорого и масштабируется с количеством вершин на многоangularьник.

К счастью, есть отличная новая работа, которая описывает альтернативный метод, который не только быстрее (для больших и сложных сеток), но также обобщает обмотку ' Концепция порядка для конструкций за пределами многоangularьных сеток, таких как облака точек и супы многоangularьников, изо-поверхности и поверхности с заданными точками, где связность может даже не быть определена!

Как указано в статье, метод создает представление иерархического дерева расщепления, которое постепенно уточняется с учетом ориентации родительского диполя при каждой операции расщепления. Тогда нормаль многоangularьника будет просто интегрированием (средним) по всем диполям (то есть парам точек + нормалей) многоangularьника.

Для людей, которые имеют дело с нечистыми данными сетки /pcl от сканеров Lidar или других источников, это может быть определено. быть переломным моментом.

Ответ 5

Для таких, как я, кто сталкивался с этим вопросом, ваш ответ может быть следующим:

// Compute Vertex Normals
std::vector<sf::Glsl::Vec3> verticesNormal;
verticesNormal.resize(verticesCount);

for (i = 0; i < indices.size(); i += 3)
{
    // Get the face normal
    auto vector1 = verticesPos[indices[(size_t)i + 1]] - verticesPos[indices[i]];
    auto vector2 = verticesPos[indices[(size_t)i + 2]] - verticesPos[indices[i]];
    auto faceNormal = sf::VectorCross(vector1, vector2);
    sf::Normalize(faceNormal);

    // Add the face normal to the 3 vertices normal touching this face
    verticesNormal[indices[i]] += faceNormal;
    verticesNormal[indices[(size_t)i + 1]] += faceNormal;
    verticesNormal[indices[(size_t)i + 2]] += faceNormal;
}

// Normalize vertices normal
for (i = 0; i < verticesNormal.size(); i++)
    sf::Normalize(verticesNormal[i]);