简体   繁体   English

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

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

I'm trying to create geometry to represent the Earth in OpenGL. 我正在尝试创建几何体以在OpenGL中表示地球。 I have what's more or less a sphere (closer to the elliptical geoid that Earth is though). 我有一个或多或少的球体(尽管接近地球的椭圆大地水准面)。 I map a texture of the Earth's surface (that's probably a mercator projection or something similar). 我绘制了地球表面的纹理(这可能是一个墨卡托投影或类似的东西)。 The texture's UV coordinates correspond to the geometry's latitude and longitude. 纹理的UV坐标对应于几何的纬度和经度。 I have two issues that I'm unable to solve. 我有两个问题,我无法解决。 I am using OpenSceneGraph but I think this is a general OpenGL / 3D programming question. 我正在使用OpenSceneGraph,但我认为这是一个普通的OpenGL / 3D编程问题。

  • There's a texture seam that's very apparent. 有一个非常明显的纹理接缝。 I'm sure this occurs because I don't know how to map the UV coordinates to XYZ where the seam occurs. 我确定这是因为我不知道如何将UV坐标映射到发生接缝的XYZ。 I only map UV coords up to the last vertex before wrapping around... You'd need to map two different UV coordinates to the same XYZ vertex to eliminate the seam. 我只将UV coords映射到最后一个顶点然后环绕...你需要将两个不同的UV坐标映射到同一个XYZ顶点以消除接缝。 Is there a commonly used trick to get around this, or am I just doing it wrong? 是否有一个常用的技巧来解决这个问题,或者我只是做错了?

  • There's crazy swirly distortion going on at the poles. 两极发生疯狂的扭曲变形。 I'm guessing this because I map a single UV point at the poles (for Earth, I use [0.5,1] for the North Pole, and [0.5,0] for the South Pole). 我猜这是因为我在极点映射一个UV点(对于地球,我使用[0.5,1]作为北极,而[0.5,0]作为南极)。 What else would you do though? 你会做什么呢? I can sort of live with this... but its extremely noticeable at lower resolution meshes. 我可以接受这个......但是在较低分辨率的网格中它非常明显。

I've attached an image to show what I'm talking about. 我附上了一张图片来展示我在说什么。

我很害怕渲染地球

The general way this is handled is by using a cube map , not a 2D texture. 处理它的一般方法是使用立方体贴图 ,而不是2D纹理。

However, if you insist on using a 2D texture, you have to create a break in your mesh's topology. 但是,如果您坚持使用2D纹理,则必须在网格拓扑中创建一个中断。 The reason you get that longitudinal line is because you have one vertex with a texture coordinate of something like 0.9 or so, and its neighboring vertex has a texture coordinate of 0.0. 获得该纵向线的原因是因为您有一个顶点的纹理坐标大小为0.9左右,其相邻顶点的纹理坐标为0.0。 What you really want is that the 0.9 one neighbors a 1.0 texture coordinate. 你真正想要的是0.9一个与1.0纹理坐标相邻。

Doing this means replicating the position down one line of the sphere. 这样做意味着将位置复制到球体的一行。 So you have the same position used twice in your data. 因此,您在数据中使用两次相同的位置。 One is attached to a texture coordinate of 1.0 and neighbors a texture coordinate of 0.9. 一个附加到纹理坐标1.0并且邻近纹理坐标0.9。 The other has a texture coordinate of 0.0, and neighbors a vertex with 0.1. 另一个的纹理坐标为0.0,并且顶点为0.1。

Topologically, you need to take a longitudinal slice down your sphere. 拓扑学上,您需要沿着球体纵向切片。

Your link really helped me out, furqan, thanks. 你的链接真的帮了我,furqan,谢谢。
Why couldn't you figure it out? 你为什么不弄明白? A point where I stumbled was, that I didn't know you can exceed the [0,1] interval when calculating the texture coordinates. 我偶然发现的一点是,我不知道在计算纹理坐标时你可以超过[0,1]间隔。 That makes it a lot easier to jump from one side of the texture to the other with OpenGL doing all the interpolation and without having to calculate the exact position where the texture actually ends. 这使得从纹理的一侧跳到另一侧更容易,使用OpenGL进行所有插值,而无需计算纹理实际结束的确切位置。

It took a long time to figure this extremely annoying issue out. 花了很长时间才弄清楚这个非常烦人的问题。 I'm programming with C# in Unity and I didn't want to duplicate any vertices. 我在Unity中用C#编程,我不想复制任何顶点。 (Would cause future issues with my concept) So I went with the shader idea and it works out pretty well. (这将导致我的概念未来出现问题)所以我选择了着色器的想法并且效果非常好。 Although I'm sure the code could use some heavy duty optimization, I had to figure out how to port it over to CG from this but it works. 虽然我确信代码可以使用一些重型优化,但我必须弄清楚如何将它移植到CG,但它可以工作。 This is in case someone else runs across this post, as I did, looking for a solution to the same problem. 这是以防其他人像我一样在这篇文章中运行,寻找同一问题的解决方案。

    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
    }
}

} }

As Nicol Bolas said, some triangles have UV coordinates going from ~0.9 back to 0, so the interpolation messes the texture around the seam. 正如Nicol Bolas所说,一些三角形的UV坐标从~0.9回到0,因此插值会使接缝周围的纹理混乱。 In my code, I've created this function to duplicate the vertices around the seam. 在我的代码中,我创建了这个函数来复制接缝周围的顶点。 This will create a sharp line splitting those vertices. 这将创建一条分割这些顶点的锐利线。 If your texture has only water around the seam (the Pacific ocean?), you may not notice this line. 如果您的纹理在接缝周围只有水(太平洋?),您可能不会注意到这条线。 Hope it helps. 希望能帮助到你。

/**
 *  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;
    }

}

You can also go a dirty way: interpolate X,Y positions in between vertex shader and fragment shader and recalculate correct texture coordinate in fragment shader. 您也可以采用肮脏的方式:在顶点着色器和片段着色器之间插入X,Y位置,并在片段着色器中重新计算正确的纹理坐标。 This may be somewhat slower, but it doesn't involve duplicate vertexes and it's simplier, I think. 这可能有点慢,但它不涉及重复的顶点,而且我认为它更简单。

For example: 例如:
vertex shader: 顶点着色器:

#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;
}

fragment shader: 片段着色器:

#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);
}

One approach is like in the accepted answer. 一种方法就像在接受的答案中一样。 In the code generating the array of vertex attributes you will have a code like this: 在生成顶点属性数组的代码中,您将得到如下代码:

// 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.;
    }
}

If you have triangles which are not meridian-aligned you will also want glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 如果您的三角形不是子午线对齐,您还需要glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); . You also need to use glDrawArrays since vertices with the same position will have different texture coords. 您还需要使用glDrawArrays因为具有相同位置的顶点将具有不同的纹理坐标。

I think the better way to go is to eliminate the root of all evil, which is texture coords interpolation in this case. 我认为更好的方法是消除所有邪恶的根源,在这种情况下是纹理坐标插值。 Since you know basically all about your sphere/ellipsoid, you can calculate texture coords, normals, etc. in the fragment shader based on position. 由于您基本上了解球体/椭球的所有信息,因此您可以根据位置计算碎片着色器中的纹理坐标,法线等。 This means that your CPU code generating vertex attributes will be much simpler and you can use indexed drawing again. 这意味着生成顶点属性的CPU代码将更加简单,您可以再次使用索引绘图。 And I don't think this approach is dirty. 我不认为这种方法很脏。 It's clean. 它很干净。

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

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