Я пытаюсь сделать куб, который нерегулярно триангулирован, но практически копланарен, правильно оттеняет.
Вот текущий результат, который я имею:
С каркасом:
Нормы, рассчитанные в моей программе:
Нормалы, рассчитанные по 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);
РЕДАКТИРОВАТЬ: Извините, что не достаточно ясно. Куб является просто примером. Мои требования заключаются в том, что затенение работает для любого вида сетки. Те, у кого очень острые края, и те, которые очень органичны (например, люди или животные).
3 ответа
Как уже упоминалось в других ответах, проблема заключается в ваших нормалей сетки. Вычисление среднего нормального, как вы делаете в настоящее время, это то, что вы хотели бы сделать для гладкого объекта, как сфера.
cgal
имеет для этого функцию :: compute_vertex_normal
Для куба нужно перпендикулярно граням нормалей
cgal
для этого тоже есть фунтоин
Для отладки нормалей вы можете просто установить fragColor = vec4(norm,1);
в mainmesh.frag
. Здесь кубы слева имеют усредненные (гладкие) нормали, а справа - грани (плоские) нормали:
И затененные они выглядят так :
Затенение должно работать для любого вида сетки (куба или любой органической сетки)
Для этого вы можете использовать что-то вроде per_corner_normals whitch:
Реализует простую схему, которая вычисляет угловые нормали как средние от нормалей граней, падающих на соответствующую вершину, которые не отклоняются более чем на определенный двугранный угол (например, 20 °)
И вот как это выглядит под углом 1 °, 20 °, 100 °:
На вашем изображении мы видим, что внутренний треугольник (тот, который не имеет точки на краях куба в верхней левой четверти) имеет однородный цвет.
Моя интерпретация заключается в том, что треугольники, имеющие точки на ребре / углу куба, разделяют одну и ту же вершину, а затем разделяют одну и ту же нормаль, а некоторые усредняют как нормаль. Так что это не перпендикулярно лицам.
Чтобы отладить это, вы должны создать простую геометрию куба с 6 гранями и 2 треугольниками на грани. Отсюда получается 12 треугольников.
Два варианта:
- Если у вас 8 вершин в геометрии, угол распределяется между треугольниками с разной гранью, и проблема возникла из генератора геометрии.
- Если у вас есть 6 × 4 = 24 вершины в геометрии, истина лежит в другом месте.
Проблема четко объясняется изображением «Нормалы, вычисленные в моей программе» из вашего вопроса. Нормальные векторы в углах и краях куба не являются нормальными перпендикулярно к граням:
Для правильного зеркального отражения на плоских гранях нормальные векторы должны быть перпендикулярными по сторонам куб.
Координата вершины и ее нормальный вектор из набора из 6 компонентов (x, y, z, nx, ny, nz). Координата вершины на ребре куба смежна с 2 сторонами куба и 2 (гранями) нормальными векторами. 8 координат вершин на 8 углах куба смежны с 3 сторонами (3 нормальных вектора) каждая.
Чтобы определить атрибуты вершины с векторами нормали лица (перпендикулярно стороне), вы должны определить несколько кортежей с одинаковыми координатами вершины, но разными векторами нормали. Вы должны использовать различные кортежи атрибутов, чтобы сформировать треугольные примитивы на разных сторонах куба.
Например Если вы определили куб с левой, передней и нижней координатами (-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. Почему где-то посередине плоскости есть «вершина»? Вероятно, самолет тесселяции. Но если план основан на тесселяции, почему нормальные векторы тоже не интерполируются во время процесса тесселяции?
Похожие вопросы
Новые вопросы
c++
C ++ - это язык программирования общего назначения. Первоначально он был разработан как расширение C и имеет аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде (который должен быть) скомпилирован с помощью компилятора C ++. Используйте тег для конкретной версии для вопросов, связанных с конкретной версией стандарта [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] или [C ++ 23] и т. Д. .