【问题标题】:Seam issue when mapping a texture to a sphere in OpenGL在 OpenGL 中将纹理映射到球体时出现接缝问题
【发布时间】:2012-03-01 06:15:18
【问题描述】:

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

  • 有一个非常明显的纹理接缝。我确定会发生这种情况,因为我不知道如何将 UV 坐标映射到出现接缝的 XYZ。我只在环绕之前将 UV 坐标映射到最后一个顶点……您需要将两个不同的 UV 坐标映射到同一个 XYZ 顶点以消除接缝。是否有一个常用的技巧来解决这个问题,还是我做错了?

  • 两极出现疯狂的漩涡状扭曲。我猜这是因为我在两极映射了一个 UV 点(对于地球,我使用 [0.5,1] 表示北极,使用 [0.5,0] 表示南极)。你还会做什么?我可以忍受这个......但它在较低分辨率的网格中非常明显。

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

【问题讨论】:

标签: c++ opengl geometry


【解决方案1】:

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

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

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

从拓扑上讲,你需要在你的球体上进行纵向切片。

【讨论】:

  • 这太糟糕了。感谢您的提醒。作为快速跟进,您是否有资源为球体实现立方体贴图?
  • @Pris:球体的立方体贴图很简单。您可以直接使用顶点位置(或法线)作为立方体贴图中的纹理坐标。立方体贴图本身由排列在立方体中的 6 个样方组成。纹理坐标确定从立方体中心开始的方向,与立方体本身的交点产生纹素。
【解决方案2】:

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

【讨论】:

  • 这非常有用。谢谢!真正简单优雅的解决方案!
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    花了很长时间才解决这个极其烦人的问题。我在 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
        }
    }
    

    }

    【讨论】:

    • 我建议使用法线而不是位置,以防您的球体网格的直径不是 1,原点不是 0,0,0
    【解决方案5】:

    正如 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;
        }
    
    }
    

    【讨论】:

      【解决方案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,因为具有相同位置的顶点将具有不同的纹理坐标。

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

      【讨论】:

        猜你喜欢
        • 2013-07-03
        • 1970-01-01
        • 2013-12-07
        • 2017-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-14
        • 1970-01-01
        相关资源
        最近更新 更多