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

Проблема с швом при сопоставлении текстуры с сферой в OpenGL

Я пытаюсь создать геометрию для представления Земли в OpenGL. У меня есть более или менее сфера (ближе к эллиптическому геоиду, который есть на Земле). Я рисую текстуру поверхности Земли (это, вероятно, проекция меркатора или что-то подобное). Ультрафиолетовые координаты соответствуют геометрии широты и долготы. У меня есть два вопроса, которые я не могу решить. Я использую OpenSceneGraph, но я думаю, что это общий вопрос программирования OpenGL/3D.

  • Там текстурный шов, который очень ясен. Я уверен, что это происходит, потому что я не знаю, как сопоставить UV-координаты XYZ, где происходит шов. Я только сопоставляю UV-координаты до последней вершины, прежде чем обертывать вокруг... Вам нужно будет сопоставить две разные UV-координаты с одной и той же вершиной XYZ, чтобы устранить шов. Существует ли обычно используемый трюк, чтобы обойти это, или я просто делаю это неправильно?

  • Там сумасшедшие искажения на полюсах. Я предполагаю это, потому что я сопоставляю одну точку UV на полюсах (для Земли я использую [0,5,1] для Северного полюса и [0,5,0] для Южного полюса). Что еще вы могли бы сделать? Я могу жить с этим... но его чрезвычайно заметно в сетках с более низким разрешением.

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

I suck at rendering Earth

4b9b3361

Ответ 1

Общий способ обработки - использовать кубическую карту, а не 2D-текстуру.

Однако, если вы настаиваете на использовании 2D-текстуры, вам нужно создать разрыв в вашей топологии сетки. Причина, по которой вы получаете эту продольную линию, состоит в том, что у вас есть одна вершина с координатой текстуры примерно 0,9 или около того, а ее соседняя вершина имеет координату текстуры 0.0. То, что вы действительно хотите, это то, что 0,9 соседствует с текстурной координатой 1.0.

Выполнение этого означает репликацию позиции по одной линии сферы. Таким образом, у вас есть такая же позиция, которая используется дважды в ваших данных. Один прикреплен к координате текстуры 1.0 и соседствует с координатой текстуры 0,9. Другая имеет координату текстуры 0.0 и соседствует с вершиной с 0,1.

Топологически вам нужно взять продольный срез вниз по вашей сфере.

Ответ 2

Ваша ссылка действительно помогла мне, furqan, спасибо.
Почему ты не мог понять это? Точка, где я споткнулся, была, что я не знал, что вы можете превысить интервал [0,1] при вычислении координат текстуры. Это значительно облегчает переход от одной стороны текстуры к другой с помощью OpenGL, выполняющего всю интерполяцию, и без необходимости вычислять точное положение, где текстура действительно заканчивается.

Ответ 3

Понадобилось много времени, чтобы понять эту крайне неприятную проблему. Я программирую с С# в Unity, и я не хотел дублировать любые вершины. (Может быть, будущие проблемы с моей концепцией). Поэтому я пошел с идеей шейдера, и все получилось очень хорошо. Хотя я уверен, что код может использовать некоторую интенсивную оптимизацию, мне пришлось выяснить, как перенести его на CG, но он работает. Это на случай, если кто-то еще столкнется с этим сообщением, как и я, ища решение той же проблемы.

    Shader "Custom/isoshader" {
Properties {
        decal ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Pass {
        Fog { Mode Off }

        CGPROGRAM

        #pragma vertex vert
        #pragma fragment frag
        #define PI 3.141592653589793238462643383279

        sampler2D decal;

        struct appdata {
            float4 vertex : POSITION;
            float4 texcoord : TEXCOORD0;
        };

        struct v2f {
            float4 pos : SV_POSITION;
            float4 tex : TEXCOORD0;
            float3 pass_xy_position : TEXCOORD1;
        };

        v2f vert(appdata v){
            v2f  o;
            o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
            o.pass_xy_position = v.vertex.xyz;
            o.tex = v.texcoord;
            return o;
        }

        float4 frag(v2f i) : COLOR {
            float3 tc = i.tex;
            tc.x = (PI + atan2(i.pass_xy_position.x, i.pass_xy_position.z)) / (2 * PI);
            float4 color = tex2D(decal, tc);
            return color;
        }

        ENDCG
    }
}

}

Ответ 4

Как сказал Никол Болас, некоторые треугольники имеют УФ-координаты, идущие от ~ 0,9 обратно до 0, поэтому интерполяция разрушает текстуру вокруг шва. В моем коде я создал эту функцию для дублирования вершин вокруг шва. Это создаст резкую линию, разделяющую эти вершины. Если у вашей текстуры есть только вода вокруг шва (Тихоокеанский океан?), Вы можете не заметить эту линию. Надеюсь, что это поможет.

/**
 *  After spherical projection, some triangles have vertices with
 *  UV coordinates that are far away (0 to 1), because the Azimuth
 *  at 2*pi = 0. Interpolating between 0 to 1 creates artifacts
 *  around that seam (the whole texture is thinly repeated at
 *  the triangles around the seam).
 *  This function duplicates vertices around the seam to avoid
 *  these artifacts.
 */
void PlatonicSolid::SubdivideAzimuthSeam() {
    if (m_texCoord == NULL) {
        ApplySphericalProjection();
    }

    // to take note of the trianges in the seam
    int facesSeam[m_numFaces];

    // check all triangles, looking for triangles with vertices
    // separated ~2π. First count.
    int nSeam = 0;
    for (int i=0;i < m_numFaces; ++i) {
        // check the 3 vertices of the triangle
        int a = m_faces[3*i];
        int b = m_faces[3*i+1];
        int c = m_faces[3*i+2];
        // just check the seam in the azimuth
        float ua = m_texCoord[2*a];
        float ub = m_texCoord[2*b];
        float uc = m_texCoord[2*c];
        if (fabsf(ua-ub)>0.5f || fabsf(ua-uc)>0.5f || fabsf(ub-uc)>0.5f) {
            //test::printValue("Face: ", i, "\n");
            facesSeam[nSeam] = i;
            ++nSeam;
        }
    }

    if (nSeam==0) {
        // no changes
        return;
    }

    // reserve more memory
    int nVertex = m_numVertices;
    m_numVertices += nSeam;
    m_vertices = (float*)realloc((void*)m_vertices, 3*m_numVertices*sizeof(float));
    m_texCoord = (float*)realloc((void*)m_texCoord, 2*m_numVertices*sizeof(float));

    // now duplicate vertices in the seam
    // (the number of triangles/faces is the same)
    for (int i=0; i < nSeam; ++i, ++nVertex) {
        int t = facesSeam[i]; // triangle index
        // check the 3 vertices of the triangle
        int a = m_faces[3*t];
        int b = m_faces[3*t+1];
        int c = m_faces[3*t+2];
        // just check the seam in the azimuth
        float u_ab = fabsf(m_texCoord[2*a] - m_texCoord[2*b]);
        float u_ac = fabsf(m_texCoord[2*a] - m_texCoord[2*c]);
        float u_bc = fabsf(m_texCoord[2*b] - m_texCoord[2*c]);
        // select the vertex further away from the other 2
        int f = 2;
        if (u_ab >= 0.5f && u_ac >= 0.5f) {
            c = a;
            f = 0;
        } else if (u_ab >= 0.5f && u_bc >= 0.5f) {
            c = b;
            f = 1;
        }

        m_vertices[3*nVertex] = m_vertices[3*c];      // x
        m_vertices[3*nVertex+1] = m_vertices[3*c+1];  // y
        m_vertices[3*nVertex+2] = m_vertices[3*c+2];  // z
        // repeat u from texcoord
        m_texCoord[2*nVertex] = 1.0f - m_texCoord[2*c];
        m_texCoord[2*nVertex+1] = m_texCoord[2*c+1];
        // change this face so all the vertices have close UV
        m_faces[3*t+f] = nVertex;
    }

}

Ответ 5

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

Например:
вершинный шейдер:

#version 150 core
uniform mat4 projM;
uniform mat4 viewM;
uniform mat4 modelM;
in vec4 in_Position;
in vec2 in_TextureCoord;
out vec2 pass_TextureCoord;
out vec2 pass_xy_position;
void main(void) {
    gl_Position = projM * viewM * modelM * in_Position;
    pass_xy_position = in_Position.xy; // 2d spinning interpolates good!
    pass_TextureCoord = in_TextureCoord;
}

фрагментарный шейдер:

#version 150 core
uniform sampler2D texture1;
in vec2 pass_xy_position;
in vec2 pass_TextureCoord;
out vec4 out_Color;

#define PI 3.141592653589793238462643383279

void main(void) {
    vec2 tc = pass_TextureCoord;
    tc.x = (PI + atan(pass_xy_position.y, pass_xy_position.x)) / (2 * PI); // calculate angle and map it to 0..1
    out_Color = texture(texture1, tc);
}

Ответ 6

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

// FOR EVERY TRIANGLE
const float threshold = 0.7;
if(tcoords_1.s > threshold || tcoords_2.s > threshold || tcoords_3.s > threshold)
{
    if(tcoords_1.s < 1. - threshold)
    {
        tcoords_1.s += 1.;
    }
    if(tcoords_2.s < 1. - threshold)
    {
        tcoords_2.s += 1.;
    }
    if(tcoords_3.s < 1. - threshold)
    {
        tcoords_3.s += 1.;
    }
}

Если у вас есть треугольники, которые не выровнены по меридиану, вам также понадобится glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);. Вам также нужно использовать glDrawArrays, поскольку вершины с одинаковой позицией будут иметь разные текстурные координаты.

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