Что не так с моим картированием нормалей? Я думаю это мои касательные

edit: вы можете начать с "Edit 3", потому что я решил многое из этого

Вот скриншот моей обычной кубической карты, примененной к икосфере:

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

Касательные для моей икосферы с кубической картой генерируются с помощью следующего кода. m_indices из std::vector индексов в std::vector вершин в m_vertices.

std::vector<glm::vec3> storedTan(m_vertices.size(),glm::vec3(0,0,0));

// tangents
for(int i = 0; i < m_indices.size(); i+=3)
{
    int i1 = m_indices[i];
    int i2 = m_indices[i+1];
    int i3 = m_indices[i+2];

    VertexData v1 = m_vertices[i1];
    VertexData v2 = m_vertices[i2];
    VertexData v3 = m_vertices[i3];

    glm::vec3 p1 = glm::vec3(v1.position[0],v1.position[1],v1.position[2]);
    glm::vec3 p2 = glm::vec3(v2.position[0],v2.position[1],v2.position[2]);
    glm::vec3 p3 = glm::vec3(v3.position[0],v3.position[1],v3.position[2]);

    glm::vec3 t1 = glm::vec3(v1.tcoords[0],v1.tcoords[1],v1.tcoords[2]);
    glm::vec3 t2 = glm::vec3(v2.tcoords[0],v2.tcoords[1],v2.tcoords[2]);
    glm::vec3 t3 = glm::vec3(v3.tcoords[0],v3.tcoords[1],v3.tcoords[2]);

    std::function<glm::vec2(glm::vec3)> get_uv = [=](glm::vec3 STR)
    {
        float sc, tc, ma;
        float x = std::abs(STR.x);
        float y = std::abs(STR.y);
        float z = std::abs(STR.z);
        if(x > y && x > z)
        {
            if(STR.x > 0)
            {
                sc = -STR.z;
                tc = -STR.y;
                ma = STR.x;
            }
            else
            {
                sc = STR.z;
                tc = -STR.t;
                ma = STR.x;
            }
        }
        else if(y > z)
        {
            if(STR.y > 0)
            {
                sc = STR.x;
                tc = STR.z;
                ma = STR.y;
            }
            else
            {
                sc = STR.x;
                tc = -STR.z;
                ma = STR.y;
            }
        }
        else
        {
            if(STR.z > 0)
            {
                sc = STR.x;
                tc = -STR.y;
                ma = STR.z;
            }
            else
            {
                sc = -STR.x;
                tc = -STR.y;
                ma = STR.z;
            }
        }
        return glm::vec2((sc/std::abs(ma) + 1.0) / 2.0,(tc/std::abs(ma) + 1.0) / 2.0);
    };

    glm::vec2 uv1 = get_uv(t1);
    glm::vec2 uv2 = get_uv(t2);
    glm::vec2 uv3 = get_uv(t3);

    glm::vec3 edge1 = p2 - p1;
    glm::vec3 edge2 = p3 - p1;

    glm::vec2 tedge1 = uv2 - uv1;
    glm::vec2 tedge2 = uv3 - uv1;

    float r = 1.0f / (tedge1.x * tedge2.y - tedge2.x - tedge1.y);

    glm::vec3 sdir((tedge2.y * edge1.x - tedge1.y * edge2.x) * r,
                   (tedge2.y * edge1.y - tedge1.y * edge2.y) * r,
                   (tedge2.y * edge1.z - tedge1.y * edge2.z) * r);

    glm::vec3 tdir((tedge1.x * edge2.x - tedge2.x * edge1.x) * r,
                   (tedge1.x * edge2.y - tedge2.x * edge1.y) * r,
                   (tedge1.x * edge2.z - tedge2.x * edge1.z) * r);

    m_vertices[i1].tangent[0] += sdir.x;
    m_vertices[i1].tangent[1] += sdir.y;
    m_vertices[i1].tangent[2] += sdir.z;

    m_vertices[i2].tangent[0] += sdir.x;
    m_vertices[i2].tangent[1] += sdir.y;
    m_vertices[i2].tangent[2] += sdir.z;

    m_vertices[i3].tangent[0] += sdir.x;
    m_vertices[i3].tangent[1] += sdir.y;
    m_vertices[i3].tangent[2] += sdir.z;

    storedTan[i1] += sdir;
    storedTan[i2] += sdir;
    storedTan[i3] += sdir;
}

for(int i = 0; i < m_vertices.size(); ++i)
{
    glm::vec3 n = glm::vec3(m_vertices[i].normal[0],m_vertices[i].normal[1],m_vertices[i].normal[2]);
    glm::vec3 t = glm::vec3(m_vertices[i].tangent[0],m_vertices[i].tangent[1],m_vertices[i].tangent[2]);

    glm::vec3 newT = glm::normalize(t - n * glm::dot(n,t));
    m_vertices[i].tangent[0] = newT.x;
    m_vertices[i].tangent[1] = newT.y;
    m_vertices[i].tangent[2] = newT.z;
    m_vertices[i].tangent[3] = (glm::dot(glm::cross(n,t), storedTan[i]) < 0.0f) ? -1.0f : 1.0f;
}

Мои VertexData выглядят так, BTW:

struct VertexData
{
    GLfloat position[4];
    GLfloat normal[3];
    GLfloat tcoords[3];
    GLfloat tangent[4];
};

Я знаю, что текущие tcoords, position и normal в порядке (иначе вы бы не увидели снимок экрана выше).

Тогда мой вершинный шейдер выглядит так:

#version 400

layout (location = 0) in vec4 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec3 in_UV;
layout (location = 3) in vec4 in_tangent;

struct PointLight
{
    bool active;

    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 lightMVP;

uniform PointLight uLight;

smooth out vec3 ex_UV;
out vec3 ex_normal;
out vec3 ex_positionCameraSpace;
out vec3 ex_originalPosition;
out vec3 ex_positionWorldSpace;
out vec4 ex_positionLightSpace;
out vec3 ex_tangent;
out vec3 ex_binormal;

out PointLight ex_light;

void main()
{
    gl_Position = projection * view * model * in_position;

    ex_UV = in_UV;
    ex_normal = mat3(transpose(inverse(view * model))) * in_normal;
    ex_positionCameraSpace =  vec3(view * model * in_position);
    ex_originalPosition = vec3(in_position.xyz);
    ex_positionWorldSpace = vec3(model*in_position);
    ex_positionLightSpace = lightMVP * model * in_position;

    ex_tangent = mat3(transpose(inverse(view * model))) * in_tangent.xyz;
    ex_binormal = cross(ex_normal,ex_tangent);

    // provide the fragment shader with a light in view space rather than world space
    PointLight p = uLight;
    p.position = vec3(view * vec4(p.position,1.0));
    ex_light = p;
}

И, наконец, мой фрагментный шейдер выглядит так:

#version 400

layout (location = 0) out vec4 color;

struct Material
{
    bool useMaps;
    samplerCube diffuse;
    samplerCube specular;
    samplerCube normal;
    float shininess;
    vec4 color1;
    vec4 color2;
};

struct PointLight
{
    bool active;

    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

uniform Material uMaterial;

smooth in vec3 ex_UV;
in vec3 ex_normal;
in vec3 ex_positionCameraSpace;
in vec3 ex_originalPosition;
in vec3 ex_positionWorldSpace;
in vec4 ex_positionLightSpace;

in vec3 ex_tangent;
in vec3 ex_binormal;

in PointLight ex_light;

/* ******************
Provides a better lookup into a cubemap
******************* */
vec3 fix_cube_lookup(vec3 v, float cube_size)
{
    float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
    float scale = (cube_size - 1) / cube_size;
    if (abs(v.x) != M)
        v.x *= scale;
    if (abs(v.y) != M)
        v.y *= scale;
    if (abs(v.z) != M)
        v.z *= scale;
    return v;
}

/* *********************
Calculates the color when using a point light. Uses shadow map
********************* */
vec3 CalcPointLight(PointLight light, Material mat, vec3 normal, vec3 fragPos, vec3 originalPos, vec3 viewDir)
{
    // replace the normal with lookup normal. This is now in tangent space
    vec3 textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.normal,0).x);
    normal = texture(mat.normal,textureLookup).rgb;

    // the direction the light is in in the light position - fragpos
    // light dir and view dir are now in tangent space
    vec3 lightDir = transpose(mat3(ex_tangent,ex_binormal,ex_normal)) * normalize(fragPos - light.position);
    viewDir = transpose(mat3(ex_tangent,ex_binormal,ex_normal)) * viewDir;

    // get the diffuse color
    textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.diffuse,0).x);
    vec3 diffuseMat = vec3(0.0);
    if(mat.useMaps)
        diffuseMat = texture(mat.diffuse,textureLookup).rgb;
    else
        diffuseMat = mat.color1.rgb;

    // get the specular color
    textureLookup = fix_cube_lookup(normalize(ex_originalPosition),textureSize(mat.specular,0).x);
    vec3 specularMat = vec3(0.0);
    if(mat.useMaps)
        specularMat = texture(mat.specular,textureLookup).rgb;
    else
        specularMat = mat.color2.rgb;

    // the ambient color is the amount of normal ambient light hitting the diffuse texture
    vec3 ambientColor = light.ambient * diffuseMat;

    // Diffuse shading
    float diffuseFactor = dot(normal, -lightDir);
    vec3 diffuseColor = vec3(0,0,0);
    vec3 specularColor = vec3(0,0,0);
    if(diffuseFactor > 0)
        diffuseColor = light.diffuse * diffuseFactor * diffuseMat;

    // Specular shading
    vec3 reflectDir = normalize(reflect(lightDir, normal));
    float specularFactor = pow(dot(viewDir,reflectDir), mat.shininess);
    if(specularFactor > 0 && diffuseFactor > 0)
        specularColor = light.specular * specularFactor * specularMat;

    float lightDistance = length(fragPos - light.position);
    float attenuation = light.constant + light.linear * lightDistance + light.quadratic * lightDistance * lightDistance;

    return ambientColor + (diffuseColor + specularColor) / attenuation;
}

void main(void)
{
    vec3 norm = normalize(ex_normal);
    vec3 viewDir = normalize(-ex_positionCameraSpace);

    vec3 result = CalcPointLight(ex_light,uMaterial,norm,ex_positionCameraSpace, ex_positionWorldSpace,viewDir);

    color = vec4(result,1.0);
}

Насколько я могу судить:

  1. Мои касательные рассчитываются правильно.
  2. Моя карта нормалей для меня выглядит как карта нормалей.
  3. Я меняю направление света и обзора на касательное пространство, чтобы оно соответствовало моей карте нормалей.

Результат - ничего. Т.е. на экран ничего не рисуется. Совсем не сплошной цвет. Как будто все позади нарисовано без окклюзии.

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

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

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

Если я просто трансформирую свет по касательной матрице, я получу следующее:

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

Все это в совокупности говорит мне, что я понятия не имею, в чем я ошибаюсь.

У меня есть подозрение, что это мое касательное поколение, потому что другие части, кажется, следуют тому, что, по-видимому, говорится в каждом учебнике, который я прочитал. Касательные генерируются с учетом кубической икосферы. Итак, чтобы определить <S,T> или <U,V> 2D-координаты из обычных 3D-координат кубической карты, я:

  1. Используйте наибольшее значение, чтобы определить лицо, на котором я нахожусь
  2. Используйте код из https://www.opengl.org/registry/specs/ARB/texture_cube_map.txt для определения координат S, T.

Вот выдержка из https://www.opengl.org/registry/specs/ARB/texture_cube_map.txt, о котором я говорю.

  major axis
  direction     target                             sc     tc    ma
  ----------    -------------------------------    ---    ---   ---
   +rx          TEXTURE_CUBE_MAP_POSITIVE_X_ARB    -rz    -ry   rx
   -rx          TEXTURE_CUBE_MAP_NEGATIVE_X_ARB    +rz    -ry   rx
   +ry          TEXTURE_CUBE_MAP_POSITIVE_Y_ARB    +rx    +rz   ry
   -ry          TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB    +rx    -rz   ry
   +rz          TEXTURE_CUBE_MAP_POSITIVE_Z_ARB    +rx    -ry   rz
   -rz          TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB    -rx    -ry   rz

 Using the sc, tc, and ma determined by the major axis direction as
 specified in the table above, an updated (s,t) is calculated as
 follows

    s   =   ( sc/|ma| + 1 ) / 2
    t   =   ( tc/|ma| + 1 ) / 2

 This new (s,t) is used to find a texture value in the determined
 face's 2D texture image using the rules given in sections 3.8.5
 and 3.8.6." ...

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

Вот они

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

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

Понятия не имею, как это исправить.

РЕДАКТИРОВАТЬ 2. Я выяснил проблему с отображением моих нормалей и т. д. Теперь это исправлено.

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

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

Что-то еще, что я изменил, - это поиск по моей карте нормалей. Я забыл вернуть диапазон от -1 до 1 (от 0 до 1).

normal = texture(mat.normal,textureLookup).rgb * 2.0 - 1.0;

Это не решает мою проблему.

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

Потому что, хотя мои касательные и бинормальные величины указывают во всех направлениях; Я все равно ожидаю, что что-то покажут, даже если это не так. Но не проникает даже окружающий цвет. (это происходит, даже если я оставлю свои lightDir и viewdir в покое. Если я просто проигнорирую нормаль вершины и ищу текстуру. Я теряю окружающий цвет) ...

РЕДАКТИРОВАТЬ 3. И последняя проблема

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

Итак, разобравшись с этим, я теперь вижу, как проявляются мои цвета. С моим красивым сексуальным картированием неровностей.

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

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

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

Снимок экрана: швы карты нормалей

РЕДАКТИРОВАТЬ 4 Во время тестирования от EDIT1 и ниже я использовал очень-очень низкополигональную сетку для своей икосферы. Так что у меня было минимальное количество подразделений.

Я хотел посмотреть, как будет выглядеть моя не совсем идеальная сфера нормалей с множеством полигонов. Это сразу выявило эту проблему:

черт возьми ...

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

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

Все еще ищу помощи у тех, кто это читает.

РЕДАКТИРОВАТЬ 4. Это было быстро. Этот сайт находится здесь http://www.geeks3d.com/20130122/normal-mapping-without-precomputed-tangent-space-vectors/ дал мне другой способ создания касательных. Хотя код кажется чем-то похожим на то, что я делал на ЦП, он не привел к тем случайно ориентированным касательным, которые создавали эти ребра из EDIT 3.

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

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

РЕДАКТИРОВАТЬ 5. Теперь я попытался изменить создание карты нормалей. Предыдущий код выглядел так:

for(int i = 0; i < 6; ++i)
{   
    float scale = 15.0;
    std::deque<glm::vec4> normalMap(textureSize*textureSize);
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            // center point
            int i11 = utils::math::get_1d_array_index_from_2d(x,y,textureSize);
            float v11 = cubeFacesHeight[i][i11].r;

            // to the left
            int i01 = utils::math::get_1d_array_index_from_2d(std::max(x-1,0),y,textureSize);
            float v01 = cubeFacesHeight[i][i01].r;

            // to the right
            int i21 = utils::math::get_1d_array_index_from_2d(std::min(x+1,textureSize-1),y,textureSize);
            float v21 = cubeFacesHeight[i][i21].r;

            // to the top
            int i10 = utils::math::get_1d_array_index_from_2d(x,std::max(y-1,0),textureSize);
            float v10 = cubeFacesHeight[i][i10].r;

            // and now the bottom
            int i12 = utils::math::get_1d_array_index_from_2d(x,std::min(y+1,textureSize-1),textureSize);
            float v12 = cubeFacesHeight[i][i12].r;

            glm::vec3 S = glm::vec3(1, 0, scale * v21 - scale * v01);
            glm::vec3 T = glm::vec3(0, 1, scale * v12 - scale * v10);

            glm::vec3 N = (glm::vec3(-S.z,-T.z,1) / std::sqrt(S.z*S.z + T.z*T.z + 1));

            N.x = (N.x+1.0)/2.0;
            N.y = (N.y+1.0)/2.0;
            N.z = (N.z+1.0)/2.0;
            normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = glm::vec4(N.x,N.y,N.z,v11);
        }
    }
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            cubeFacesHeight[i][utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)];
        }
    }
}

cubeFacesHeight - это std::array из 6 std::deque из glm::vec4. Или шесть сторон моей кубической карты. Цвета лиц - оттенки серого, я не использую поплавки по неважным причинам.

Теперь я изменил его на следующее, предупреждение, это некрасиво и долго.

for(int i = 0; i < 6; ++i)
{
    // 0 is negative X
    // 1 is positive X
    // 2 is negative Y
    // 3 is positive Y
    // 4 is negative Z
    // 5 is positive Z

    // +X:  right -Z (left),    left +Z (right),    top -Y (right),     bottom +Y (right)
    // -X:  right +Z (left),    left -Z (right),    top -Y (left),      bottom +Y (left)
    // -Z:  right -X (left),    left +X (right),    top -Y (bottom),    bottom +Y (top)
    // +Z:  right +X (left),    left -X (right),    top -Y (top),       bottom +Y (bottom)
    // -Y:  right +X (top),     left -X (top),      top +Z (top),       bottom -Z (top)
    // +Y:  right +X (bottom),  left -X (bottom),   top -Z (bottom),    bottom +Z (bottom)

    //+Z is towards, -Z is distance
    const int NEGATIVE_X = 0;
    const int NEGATIVE_Y = 2;
    const int NEGATIVE_Z = 4;
    const int POSITIVE_X = 1;
    const int POSITIVE_Y = 3;
    const int POSITIVE_Z = 5;

    float scale = 15.0;
    std::deque<glm::vec4> normalMap(textureSize*textureSize);
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            // center point
            int i11 = utils::math::get_1d_array_index_from_2d(x,y,textureSize);
            float v11 = cubeFacesHeight[i][i11].r;

            // to the left
            int i01 = utils::math::get_1d_array_index_from_2d(std::max(x-1,0),y,textureSize);
            float v01 = cubeFacesHeight[i][i01].r;
            if(x-1 < 0)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
            }

            // to the right
            int i21 = utils::math::get_1d_array_index_from_2d(std::min(x+1,textureSize-1),y,textureSize);
            float v21 = cubeFacesHeight[i][i21].r;
            if(x+1 > textureSize-1)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_X][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,y,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(y,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_X][i01].r;
                }
            }

            // to the top
            int i10 = utils::math::get_1d_array_index_from_2d(x,std::max(y-1,0),textureSize);
            float v10 = cubeFacesHeight[i][i10].r;
            if(y-1 < 0)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,x,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,x,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
            }

            // and now the bottom
            int i12 = utils::math::get_1d_array_index_from_2d(x,std::min(y+1,textureSize-1),textureSize);
            float v12 = cubeFacesHeight[i][i12].r;
            if(y+1 > textureSize-1)
            {
                if(i == NEGATIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(0,x,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == POSITIVE_X)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(textureSize-1,x,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == POSITIVE_Z)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Y][i01].r;
                }
                else if(i == NEGATIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,0,textureSize);
                    v01 = cubeFacesHeight[NEGATIVE_Z][i01].r;
                }
                else if(i == POSITIVE_Y)
                {
                    i01 = utils::math::get_1d_array_index_from_2d(x,textureSize-1,textureSize);
                    v01 = cubeFacesHeight[POSITIVE_Z][i01].r;
                }
            }

            glm::vec3 S = glm::vec3(1, 0, scale * v21 - scale * v01);
            glm::vec3 T = glm::vec3(0, 1, scale * v12 - scale * v10);

            glm::vec3 N = (glm::vec3(-S.z,-T.z,1) / std::sqrt(S.z*S.z + T.z*T.z + 1));

            N.x = (N.x+1.0)/2.0;
            N.y = (N.y+1.0)/2.0;
            N.z = (N.z+1.0)/2.0;

            normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = glm::vec4(N.x,N.y,N.z,v11);
        }
    }
    for(int x = 0; x < textureSize; ++x)
    {
        for(int y = 0; y < textureSize; ++y)
        {
            cubeFacesHeight[i][utils::math::get_1d_array_index_from_2d(x,y,textureSize)] = normalMap[utils::math::get_1d_array_index_from_2d(x,y,textureSize)];
        }
    }
}

Так что теперь я как бы "просачиваюсь" в соседнюю грань куба, чтобы выбрать высоту там при генерации карты нормалей. Это фактически увеличило внешний вид шва.

хмммм

Но это порождает собственные вопросы. Например ... "какого черта аффект усиливается?" Вы можете видеть, что теперь это своего рода эффект скоса.

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

Даже если бы я полностью перепутал грани куба, это не дало бы эффекта скоса, это было бы что-то совершенно пятнистое. Например, даже на полностью плоском участке, т. Е. Перетекание генерации карты нормалей на следующую грань будет иметь нулевой эффект, я все равно вижу массивный скос.

черт побери

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

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

Еще одно быстрое редактирование. Это изображение показывает кое-что, что, на мой взгляд, очень красноречиво. введите описание изображения здесь

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

Так что я вернулся к тому, что касалось моих проблем.


person NeomerArcana    schedule 25.05.2015    source источник
comment
@kfsone благодарит за совет, но вначале он не нуждался в твоем раздражающем комментарии. Думаю, голоса за говорят мне, что в моем вопросе нет ничего плохого. Я спрошу на сайте разработчиков игр (это хороший совет). Моды могут закрыть мой вопрос, если это хороший пример того, как не задавать вопросы.   -  person NeomerArcana    schedule 27.05.2015
comment
Если бы вы могли отправить мне свой проект полностью, весь исходный код со всеми активами, у меня не было бы проблем с просмотром источника и его проверкой. На этот вопрос сложно ответить, поскольку задействованы не только вычисления касательных, нормалей, битагентов и бинормалей, но и конструкция вашей сферы и то, как расположены вершины, а также то, как вы определяете свой координаты текстуры. (...)   -  person Francis Cugler    schedule 04.06.2015
comment
(продолжение ...) Когда я впервые начал работать с 3D-рендерингом и создавал базовые 3D-формы, такие как цилиндр, и назначал координаты текстуры, чтобы они были бесшовными и правильно работали с освещением, при определении нормалей мне приходилось учитывать, что каждая вершина, которая совместно определена между несколькими гранями треугольника, генерировала несколько нормалей. Чтобы исправить эту проблему, мне пришлось взять среднее значение для всех нормалей, сгенерированных для конкретной вершины. Я покажу иллюстрацию в разделе ответов, хотя это не ответ или решение вашей проблемы.   -  person Francis Cugler    schedule 04.06.2015
comment
Возможно, вы захотите использовать графический форум gamedev.net для подобных тем.   -  person v.oddou    schedule 04.06.2015


Ответы (3)


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

Я считаю, что ваша проблема связана с неравномерным выравниванием ваших касательных по поверхности. UV-отображение обычно является первым местом, где можно найти такие проблемы. И сопоставить сферу с 2D-изображением не так-то просто (посмотрите на все различные топологии проекции Земли, и вы поймете, что я имею в виду). В какой-то момент вы получите растяжение, края или сдвиг, и, скорее всего, некоторую комбинацию всего вышеперечисленного. Обычно с UV-картированием нужно выбрать, где вы собираетесь скрыть эти эффекты на поверхности. Для этого часто выбирают полюса планет. Одно из мест, где я бы посмотрел, - это перестроить ваши касательные и бинормали так, чтобы все они имели общую глобальную ориентацию, т. Е. тангет = север, а бинормаль = восток, нормаль обращена наружу (высота). Неоднородность ваших касательных и бинормалей играет прямую роль в артефактах, которые иногда возникают при проблемах с отображением нормалей, потому что они могут исказить эффект карты нормалей в этом месте, если карта нормалей была запечена с предположением, что все касательные и бинормали ориентированы равномерно.

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

Выгода или ортогональная нормальная векторная матрица

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


Определение равномерных, ортогональных нормальных векторных матриц

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

  1. единообразная ориентация объекта, то есть ... все они направлены в одном относительном направлении
  2. ортогональные векторы, которые ограничивают сдвиг при поиске текстуры

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

Сначала начнем с уже определенной векторной матрицы

vec3 = [1, 0, 0, 0, 1, 0, 0, 0, 1]; введите здесь описание изображения

Во-вторых, выполняйте эти операции в пространстве объектов, а не в мировом пространстве.

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

В-третьих, создайте вектор от vtx [n] до центра объекта.

Этот вектор скажет вам, насколько повернуть вашу векторную матрицу нормалей в двух направлениях:

  • Продольное вращение
  • Широтное вращение

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

В-четвертых, поверните векторную матрицу нормалей, чтобы выровнять

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

Наконец, переместите вашу векторную матрицу нормалей на расстояние

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

Промыть и повторить

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


Если вам нужно сохранить неоднородные, неортогональные UV-развертки

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


Интерполяция краевых пикселей?

В-третьих, глядя на то, как край складки карты нормалей повторяет форму кубической карты, мне было интересно, как вы интерполируете краевые пиксели для своей карты нормалей.


Поиск текстуры GLSL для кубических карт?

Кроме того, и я, возможно, просто не нашел того раздела вашего ответа, в котором вы обращаетесь к этому, но рассматривали ли вы возможность использования функции поиска по кубической карте GLSL? gvec4 texture( gsamplerCube sampler, vec3 P, [float bias]);

person Andrew    schedule 30.12.2016

Вот иллюстрация, которую я упомянул в комментарии:

Обычная иллюстрация

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

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

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

person Francis Cugler    schedule 04.06.2015
comment
Я создаю сферу как Икосферу. Таким образом, сами вершины не выровнены по граням куба. Нормали - это просто векторы направления от центра сферы. Таким образом, нет необходимости генерировать нормали для каждой грани и каждой вершины, или усреднять их, или что-то в этом роде. Они очень прямолинейны. В моих координатах текстур нет ничего плохого, так как любое другое наложение текстуры кубической карты работает отлично. Я полагаю, что швы получаются из того, как я генерирую нормали для каждой грани. - person NeomerArcana; 04.06.2015
comment
На самом деле, сегодня я получил удовлетворительный результат. thelastboundary.com/wp-content/uploads/2015/06/, взяв за основу идею rorydriscoll.com/2012/01/11/derivative-maps Я все еще пытаюсь расшифровать, что это означает в "Что теперь?" раздел, так как он, кажется, дает гораздо более плавный результат, чем пиксельный эффект, который я получаю сейчас - person NeomerArcana; 04.06.2015
comment
Хорошо, приятно слышать, что вы прогрессируете. Как я уже сказал, я не могу сказать, в чем проблема, без того, чтобы запустить проект на моей стороне и работать с некоторыми числами и уравнениями. Просто прочитав это, я не вижу ничего необычного, что бросалось бы в глаза. Я не знаю, имеет ли это значение или нет, но я заметил, что вы используете GLM, что хорошо, но против какой версии вы строите? - person Francis Cugler; 04.06.2015
comment
Поскольку использовались частные производные, они говорили об использовании их для перехода из одного координатного пространства в другое в рамках алгоритма с применением правила цепочки. Это позволяет шейдеру интерполировать между производными, когда объект находится ближе к камере обзора. Без использования правила цепочки для достижения этого, тогда, как в первом примере, когда объект приближается к камере, вы можете видеть куски блоков вместо более плавного рендеринга. - person Francis Cugler; 04.06.2015
comment
Да, я как бы понимаю это. Чего я не понимаю, так это производных текселя и экранного пространства UV. Итак, я считаю, что мне больше не следует использовать мои float значения высоты, а вместо этого я должен иметь vec2; но как мне это вычислить. Во-вторых, это производная экрана от ультрафиолета, я этого вообще не понимаю. - person NeomerArcana; 04.06.2015
comment
Это немного сложно и сложно поначалу понять, но также непросто объяснить. Что я могу понять из чтения этой страницы, это другой метод в отличие от использования касательных, где при использовании градиента с векторным полем это производные от того, что вы используете в качестве своей карты. - person Francis Cugler; 05.06.2015

Мне потребовалось много времени, чтобы понять, как вычислить касательное пространство. Может быть, способ, которым я наконец его понял, может помочь.

У вас есть три вершины v0, v1, v2. У каждого есть положение, нормальное и ув. Вычислим касательное пространство для v0. Ось z будет v0.normal. Нам нужно вычислить оси x и y.

Любая точка треугольника может быть выражена как v0.pos + (v1.pos-v0.pos) * t + (v2.pos-v0.pos) * s. Любая координата текстуры может быть выражена как v0.uv + (v1.uv - v0.uv) * t + (v2.uv - v0.uv) * s.

В касательном пространстве нам нужно, чтобы v1.uv - v0.uv = (1,0) и v2.uv-v0.uv = (0,1). Мы можем решить это s, t! для обоих случаев! И это s и t для нашей касательной и бинормали. Просто вставьте их обратно в уравнение положения, и вы получите положение, в котором uv = (0,1) и uv = (1,0). Вычтите v0.pos, и вы получите оси x и y! Также нормализуйте их.

И это ваше касательное пространство для v0. Матрица 3x3. Это не обязательно ортогонально. Но это нормально. Также вы вычисляете эту матрицу для каждой вершины для каждого треугольника, использующего эту вершину. Просто усредните их.

Интерполируйте их на матрицы вершин при рендеринге и нормализуйте их на пиксель.

Хороший способ проверить - просто отобразить столбец z - он должен быть нормальным.

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

person starmole    schedule 22.07.2016
comment
Если исходная карта нормалей была создана с использованием ортогональных касательных, нормалей и бинормалей, то вам нужно убедиться, что трехмерная поверхность действительно имеет ортогональные касательные, нормали и бинормали. Часто касательные и бинормальные векторы используются в качестве векторов поиска u и v. Т.е. ... касательная = v, а бинормал = u (или наоборот). На карте нормалей только два канала (R&G) относятся к направленности поверхностного освещения, а синий канал относится к тому, насколько нормаль к поверхности искажается каналами R&G. - person Andrew; 31.12.2016