繁体   English   中英

在OpenGL中将纹理映射到球体时出现缝隙问题

[英]Seam issue when mapping a texture to a sphere in OpenGL

我正在尝试创建几何体以在OpenGL中表示地球。 我有一个或多或少的球体(尽管接近地球的椭圆大地水准面)。 我绘制了地球表面的纹理(这可能是一个墨卡托投影或类似的东西)。 纹理的UV坐标对应于几何的纬度和经度。 我有两个问题,我无法解决。 我正在使用OpenSceneGraph,但我认为这是一个普通的OpenGL / 3D编程问题。

  • 有一个非常明显的纹理接缝。 我确定这是因为我不知道如何将UV坐标映射到发生接缝的XYZ。 我只将UV coords映射到最后一个顶点然后环绕...你需要将两个不同的UV坐标映射到同一个XYZ顶点以消除接缝。 是否有一个常用的技巧来解决这个问题,或者我只是做错了?

  • 两极发生疯狂的扭曲变形。 我猜这是因为我在极点映射一个UV点(对于地球,我使用[0.5,1]作为北极,而[0.5,0]作为南极)。 你会做什么呢? 我可以接受这个......但是在较低分辨率的网格中它非常明显。

我附上了一张图片来展示我在说什么。

我很害怕渲染地球

处理它的一般方法是使用立方体贴图 ,而不是2D纹理。

但是,如果您坚持使用2D纹理,则必须在网格拓扑中创建一个中断。 获得该纵向线的原因是因为您有一个顶点的纹理坐标大小为0.9左右,其相邻顶点的纹理坐标为0.0。 你真正想要的是0.9一个与1.0纹理坐标相邻。

这样做意味着将位置复制到球体的一行。 因此,您在数据中使用两次相同的位置。 一个附加到纹理坐标1.0并且邻近纹理坐标0.9。 另一个的纹理坐标为0.0,并且顶点为0.1。

拓扑学上,您需要沿着球体纵向切片。

你的链接真的帮了我,furqan,谢谢。
你为什么不弄明白? 我偶然发现的一点是,我不知道在计算纹理坐标时你可以超过[0,1]间隔。 这使得从纹理的一侧跳到另一侧更容易,使用OpenGL进行所有插值,而无需计算纹理实际结束的确切位置。

花了很长时间才弄清楚这个非常烦人的问题。 我在Unity中用C#编程,我不想复制任何顶点。 (这将导致我的概念未来出现问题)所以我选择了着色器的想法并且效果非常好。 虽然我确信代码可以使用一些重型优化,但我必须弄清楚如何将它移植到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
    }
}

}

正如Nicol Bolas所说,一些三角形的UV坐标从~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;
    }

}

您也可以采用肮脏的方式:在顶点着色器和片段着色器之间插入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);
}

一种方法就像在接受的答案中一样。 在生成顶点属性数组的代码中,您将得到如下代码:

// 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因为具有相同位置的顶点将具有不同的纹理坐标。

我认为更好的方法是消除所有邪恶的根源,在这种情况下是纹理坐标插值。 由于您基本上了解球体/椭球的所有信息,因此您可以根据位置计算碎片着色器中的纹理坐标,法线等。 这意味着生成顶点属性的CPU代码将更加简单,您可以再次使用索引绘图。 我不认为这种方法很脏。 它很干净。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM