Хобрук: Ваш путь к мастерству в программировании

Плохое освещение по методу Фонга

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

Вот текущий результат, который у меня есть: введите здесь описание изображения

С каркасом:

введите описание изображения здесь

Нормали, рассчитанные в моей программе:

введите описание изображения здесь

Нормали, рассчитанные meshlabjs.net:

введите описание изображения здесь

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

lighting.frag

vec4 scene_ambient = vec4(1, 1, 1, 1.0);

struct material
{
  vec4 ambient;
  vec4 diffuse;
  vec4 specular;
  float shininess;
};

material frontMaterial = material(
  vec4(0.25, 0.25, 0.25, 1.0),
  vec4(0.4, 0.4, 0.4, 1.0),
  vec4(0.774597, 0.774597, 0.774597, 1.0),
  76
);

struct lightSource
{
  vec4 position;
  vec4 diffuse;
  vec4 specular;
  float constantAttenuation, linearAttenuation, quadraticAttenuation;
  float spotCutoff, spotExponent;
  vec3 spotDirection;
};

lightSource light0 = lightSource(
  vec4(0.0,  0.0, 0.0, 1.0),
  vec4(100.0,  100.0,  100.0, 100.0),
  vec4(100.0,  100.0,  100.0, 100.0),
  0.1, 1, 0.01,
  180.0, 0.0,
  vec3(0.0, 0.0, 0.0)
);

vec4 light(lightSource ls, vec3 norm, vec3 deviation, vec3 position)
{
  vec3 viewDirection = normalize(vec3(1.0 * vec4(0, 0, 0, 1.0) - vec4(position, 1)));

  vec3 lightDirection;
  float attenuation;

  //ls.position.xyz = cameraPos;
  ls.position.z += 50;

  if (0.0 == ls.position.w) // directional light?
  {
    attenuation = 1.0; // no attenuation
    lightDirection = normalize(vec3(ls.position));
  } 
  else // point light or spotlight (or other kind of light) 
  {
      vec3 positionToLightSource = vec3(ls.position - vec4(position, 1.0));
    float distance = length(positionToLightSource);
    lightDirection = normalize(positionToLightSource);
    attenuation = 1.0 / (ls.constantAttenuation
      + ls.linearAttenuation * distance
      + ls.quadraticAttenuation * distance * distance);

    if (ls.spotCutoff <= 90.0) // spotlight?
    {
      float clampedCosine = max(0.0, dot(-lightDirection, ls.spotDirection));
      if (clampedCosine < cos(radians(ls.spotCutoff))) // outside of spotlight cone?
      {
        attenuation = 0.0;
        }
      else
        {
        attenuation = attenuation * pow(clampedCosine, ls.spotExponent);   
        }
    }
  }

  vec3 ambientLighting = vec3(scene_ambient) * vec3(frontMaterial.ambient);

  vec3 diffuseReflection = attenuation 
    * vec3(ls.diffuse) * vec3(frontMaterial.diffuse)
    * max(0.0, dot(norm, lightDirection));

  vec3 specularReflection;
  if (dot(norm, lightDirection) < 0.0) // light source on the wrong side?
  {
    specularReflection = vec3(0.0, 0.0, 0.0); // no specular reflection
  }
  else // light source on the right side
  {
    specularReflection = attenuation * vec3(ls.specular) * vec3(frontMaterial.specular)
         * pow(max(0.0, dot(reflect(lightDirection, norm), viewDirection)), frontMaterial.shininess);
  }

  return vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
}

vec4 generateGlobalLighting(vec3 norm, vec3 position)
{
  return light(light0, norm, vec3(2,0,0), position);
}

mainmesh.frag

#version 430
in vec3 f_color;
in vec3 f_normal;
in vec3 f_position;

in float f_opacity;

out vec4 fragColor;

vec4 generateGlobalLighting(vec3 norm, vec3 position);

void main()
{
  vec3 norm = normalize(f_normal);
  vec4 l0 = generateGlobalLighting(norm, f_position);

  fragColor = vec4(f_color, f_opacity) * l0;
}

Следует коду для создания вершин, нормалей и граней для рисовальщика.

m_vertices_buf.resize(m_mesh.num_faces() * 3, 3);
m_normals_buf.resize(m_mesh.num_faces() * 3, 3);
m_faces_buf.resize(m_mesh.num_faces(), 3);

std::map<vertex_descriptor, std::list<Vector3d>> map;
GLDebugging* deb = GLDebugging::getInstance();

auto getAngle = [](Vector3d a, Vector3d b) {
    double angle = 0.0;
    angle = std::atan2(a.cross(b).norm(), a.dot(b));
    return angle;
};

for (const auto& f : m_mesh.faces()) {
    auto f_hh = m_mesh.halfedge(f);
    //auto n = PMP::compute_face_normal(f, m_mesh);

    vertex_descriptor vs[3];
    Vector3d ps[3];

    int i = 0;
    for (const auto& v : m_mesh.vertices_around_face(f_hh)) {
        auto p = m_mesh.point(v);
        ps[i] = Vector3d(p.x(), p.y(), p.z());
        vs[i++] = v;
    }

    auto n = (ps[1] - ps[0]).cross(ps[2] - ps[0]).normalized();

    auto a1 = getAngle((ps[1] - ps[0]).normalized(), (ps[2] - ps[0]).normalized());
    auto a2 = getAngle((ps[2] - ps[1]).normalized(), (ps[0] - ps[1]).normalized());
    auto a3 = getAngle((ps[0] - ps[2]).normalized(), (ps[1] - ps[2]).normalized());

    auto area = PMP::face_area(f, m_mesh);

    map[vs[0]].push_back(n * a1);
    map[vs[1]].push_back(n * a2);
    map[vs[2]].push_back(n * a3);

    auto p = m_mesh.point(vs[0]);
    deb->drawLine(Vector3d(p.x(), p.y(), p.z()), Vector3d(p.x(), p.y(), p.z()) + Vector3d(n.x(), n.y(), n.z()) * 4);

    p = m_mesh.point(vs[1]);
    deb->drawLine(Vector3d(p.x(), p.y(), p.z()), Vector3d(p.x(), p.y(), p.z()) + Vector3d(n.x(), n.y(), n.z()) * 4);

    p = m_mesh.point(vs[2]);
    deb->drawLine(Vector3d(p.x(), p.y(), p.z()), Vector3d(p.x(), p.y(), p.z()) + Vector3d(n.x(), n.y(), n.z()) * 4);
}

int j = 0;
int i = 0;
for (const auto& f : m_mesh.faces()) {
    auto f_hh = m_mesh.halfedge(f);
    for (const auto& v : m_mesh.vertices_around_face(f_hh)) {
        const auto& p = m_mesh.point(v);
        m_vertices_buf.row(i) = RowVector3d(p.x(), p.y(), p.z());

        Vector3d n(0, 0, 0);

        //auto n = PMP::compute_face_normal(f, m_mesh);
        Vector3d norm = Vector3d(n.x(), n.y(), n.z());

        for (auto val : map[v]) {
            norm += val;
        }

        norm.normalize();

        deb->drawLine(Vector3d(p.x(), p.y(), p.z()), Vector3d(p.x(), p.y(), p.z()) + Vector3d(norm.x(), norm.y(), norm.z()) * 3,
            Vector3d(1.0, 0, 0));

        m_normals_buf.row(i++) = RowVector3d(norm.x(), norm.y(), norm.z());
    }

    m_faces_buf.row(j++) = RowVector3i(i - 3, i - 2, i - 1);
}

Следует коду художника:

m_vertexAttrLoc = program.attributeLocation("v_vertex");
m_colorAttrLoc = program.attributeLocation("v_color");
m_normalAttrLoc = program.attributeLocation("v_normal");

m_mvMatrixLoc = program.uniformLocation("v_modelViewMatrix");
m_projMatrixLoc = program.uniformLocation("v_projectionMatrix");
m_normalMatrixLoc = program.uniformLocation("v_normalMatrix");
//m_relativePosLoc = program.uniformLocation("v_relativePos");
m_opacityLoc = program.uniformLocation("v_opacity");
m_colorMaskLoc = program.uniformLocation("v_colorMask");

//bool for unmapping depth color
m_useDepthMap = program.uniformLocation("v_useDepthMap");
program.setUniformValue(m_mvMatrixLoc, modelView);

//uniform used for Color map to regular model switch
program.setUniformValue(m_useDepthMap, (m_showColorMap &&
    (m_showProblemAreas || m_showPrepMap || m_showDepthMap || m_showMockupMap)));

QMatrix3x3 normalMatrix = modelView.normalMatrix();
program.setUniformValue(m_normalMatrixLoc, normalMatrix);
program.setUniformValue(m_projMatrixLoc, projection);

//program.setUniformValue(m_relativePosLoc, m_relativePos);
program.setUniformValue(m_opacityLoc, m_opacity);
program.setUniformValue(m_colorMaskLoc, m_colorMask);

glEnableVertexAttribArray(m_vertexAttrLoc);
m_vertices.bind();
glVertexAttribPointer(m_vertexAttrLoc, 3, GL_DOUBLE, false, 3 * sizeof(GLdouble), NULL);
m_vertices.release();

glEnableVertexAttribArray(m_normalAttrLoc);
m_normals.bind();
glVertexAttribPointer(m_normalAttrLoc, 3, GL_DOUBLE, false, 0, NULL);
m_normals.release();

glEnableVertexAttribArray(m_colorAttrLoc);

if (m_showProblemAreas) {
    m_problemColorMap.bind();
    glVertexAttribPointer(m_colorAttrLoc, 3, GL_DOUBLE, false, 0, NULL);
    m_problemColorMap.release();
}
else if (m_showPrepMap) {
    m_prepColorMap.bind();
    glVertexAttribPointer(m_colorAttrLoc, 3, GL_DOUBLE, false, 0, NULL);
    m_prepColorMap.release();
}
else if (m_showMockupMap) {
    m_mokupColorMap.bind();
    glVertexAttribPointer(m_colorAttrLoc, 3, GL_DOUBLE, false, 0, NULL);
    m_mokupColorMap.release();
}
else {
    //m_colors.bind();
    //glVertexAttribPointer(m_colorAttrLoc, 3, GL_DOUBLE, false, 0, NULL);
    //m_colors.release();
}

m_indices.bind();
glDrawElements(GL_TRIANGLES, m_indices.size() / sizeof(int), GL_UNSIGNED_INT, NULL);
m_indices.release();


glDisableVertexAttribArray(m_vertexAttrLoc);
glDisableVertexAttribArray(m_normalAttrLoc);
glDisableVertexAttribArray(m_colorAttrLoc);

РЕДАКТИРОВАТЬ: извините за недостаточно ясность. Куб — это всего лишь пример. Мои требования заключаются в том, что затенение работает для любого типа сетки. Те, у кого очень острые края, и те, которые очень органичны (например, люди или животные).

03.02.2020


Ответы:


1

Как упоминалось в других ответах, проблема заключается в нормалях вашей сетки. Вычисление средней нормали, как вы делаете сейчас, — это то, что вы хотели бы сделать для гладкого объекта, такого как сфера.
cgal имеет для этого функцию
CGAL::Polygon_mesh_processing::compute_vertex_normal
Для куба вам нужны нормали, перпендикулярные граням
cgal для этого тоже есть функция CGAL::Polygon_mesh_processing::compute_face_normal


Для отладки нормалей вы можете просто установить fragColor = vec4(norm,1); в mainmesh.frag. Здесь кубы слева имеют усредненные (гладкие) нормали, а справа — гранные (плоские) нормали:
введите здесь описание изображения
И затененные они выглядят так:
введите здесь описание изображения


затенение должно работать для любого типа сетки (куб или любая органическая сетка)

Для этого вы можете использовать что-то вроде per_corner_normals, которое:

Реализует простую схему, которая вычисляет нормали угла как средние значения нормалей граней, инцидентных соответствующей вершине, которые не отклоняются более чем на заданный двугранный угол (например, 20 °).

А вот как это выглядит с углом 1°, 20°, 100°: введите здесь описание изображения

20.02.2020
  • Мне нравится полнота вашего ответа, но затенение должно работать для любого типа сетки (куба или любой органической сетки). В этом загвоздка. Даже если это должно включать какое-то обходное решение. 21.02.2020
  • @AlexandreSeverino Это не упоминалось в исходном вопросе. Я думаю, именно поэтому вы пытались усреднить нормали. 21.02.2020

  • 2

    Проблема ясно объяснена изображением «Нормалы, рассчитанные в моей программе» из вашего вопроса. Векторы нормалей в углах и краях куба не являются нормальными перпендикулярными граням:

    Для правильного зеркального отражения на плоских гранях векторы нормалей должны быть перпендикулярны сторонам куб.

    Координата вершины и ее вектор нормали из кортежа с 6 компонентами (x, y, z, nx, ny, nz). Координата вершины на ребре куба примыкает к 2 сторонам куба и 2 (граням) векторам нормали. Координаты 8 вершин на 8 углах куба примыкают к трем сторонам (трем векторам нормали) каждая.

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

    например Если вы определили куб с левой, передней, нижней координатой (-1, -1, -1) и правой, задней, верхней координатой (1, 1, 1), то координата вершины (-1, -1, -1) примыкает к левой, передней и нижней стороне куба:

             x  y  z   nx ny nz
    left:   -1 -1 -1   -1  0  0
    front:  -1 -1 -1    0 -1  0
    bottom: -1 -1 -1    0  0 -1
    

    Используйте кортеж атрибутов left для формирования примитивов треугольника с левой стороны, front для формирования передней части и bottom для треугольников внизу.


    В общем, вы должны решить, что вы хотите. Не существует общего подхода для всех сеток.
    Либо у вас есть мелкозернистая сетка, и вы хотите, чтобы она выглядела гладкой (например, в виде сферы). В этом случае ваш подход подходит, он создаст плавный световой переход на краях между примитивами.
    Или у вас есть сетка с четкими краями, как у куба. В этом случае вам нужно «дублировать» вершины. Если 2 (или даже больше) треугольника имеют общую координату вершины, но векторы нормали к граням разные, то вам необходимо создать отдельный кортеж для всех комбинаций координаты вершины и вектора нормали к граням.

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

    В любом случае это похоже на проблему XY. Почему где-то в середине плоскости есть «вершина»? Вероятно, самолет тесселирован. Но если план мозаичный, почему векторы нормалей тоже не интерполируются в процессе тесселяции?

    18.02.2020
  • Извините за недостаточно ясность. Куб был просто примером. Мои требования заключаются в том, что затенение работает для любого типа сетки. 21.02.2020
  • @AlexandreSeverino Да, конечно. Но этот подход можно распространить на любую сетку. Если 2 примитива треугольников имеют общие координаты вершин, но у них разные векторы нормали к граням, то вам необходимо дублировать вершины. Вы должны создать 2 кортежа. Но обратите внимание, что для круглых сеток вам это не нужно! Проблема вопроса заметна только для сетки с жесткими краями и нормалями граней, такими как куб. 21.02.2020

  • 3

    На вашем изображении мы видим, что внутренний треугольник (тот, у которого нет точки на ребрах куба, в верхней левой четверти) имеет однородный цвет.

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

    Чтобы отладить это, вы должны создать простую геометрию куба с 6 гранями и 2 треугольниками на каждой грани. Следовательно, это сделать 12 треугольников.

    Два варианта:

    • Если у вас есть 8 вершин в геометрии, угол является общим для треугольников с разными гранями, и проблема возникает из-за генератора геометрии.
    • Если у вас есть 6 × 4 = 24 вершины в геометрии, истина заключается в другом.
    04.02.2020
  • Я попробовал ваше предложение по отладке. После добавления взвешивания углов куб закрашивается правильно, когда треугольники правильные (одинаковые размеры). Но проблема остается для неправильной версии куба. 18.02.2020
  • Новые материалы

    Создание кнопочного меню с использованием HTML, CSS и JavaScript
    Вы будете создавать кнопочное меню, которое имеет состояние наведения, а также позволяет вам выбирать кнопку при нажатии на нее. Финальный проект можно увидеть в этом Codepen . Шаг 1..

    Внедрите OAuth в свои веб-приложения для повышения безопасности
    OAuth — это широко распространенный стандарт авторизации, который позволяет приложениям получать доступ к ресурсам от имени пользователя, не раскрывая его пароль. Это позволяет пользователям..

    Классы в JavaScript
    class является образцом java Script Object. Конструкция «class» позволяет определять классы на основе прототипов с чистым, красивым синтаксисом. // define class Human class Human {..

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

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

    Обзор: Машинное обучение: классификация
    Только что закончил третий курс курса 4 часть специализации по машинному обучению . Как и второй курс, он был посвящен низкоуровневой работе алгоритмов машинного обучения. Что касается..

    Разработка расширений Qlik Sense с qExt
    Использование современных инструментов веб-разработки для разработки крутых расширений Вы когда-нибудь хотели кнопку для установки переменной в приложении Qlik Sense? Когда-нибудь просили..