4
\$\begingroup\$

*****Look below for the updated source code***

I have started to to make some different types of objects and create them by code in XNA. Right now I have finished the code for creating a Icosahedron. And works grate as it should. I have managed to get it textured by some tips from this page.

Like the guy in this thread I get a seam. Not sure how to solve it properly.

seam problem

Seam problem closeup

Here is my code for what sets up the resulting Vertices and Indices

(since update this "IndexVertives" is not really what is used anymore. Check the "Init" in the update bellow)

    private void IndexVertices(List<Vector3> originalVertices, out VertexPositionNormalTexture[] outVertices, out int[] outIndices)
    {
        Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();
        List<VertexPositionNormalTexture> vertices = new List<VertexPositionNormalTexture>();
        int indexCounter = 0;

        int runTime = 0;
        foreach (Vector3 position in originalVertices)
        {
            if (!vertexIndices.ContainsKey(position))
            {
                Vector3 normal = new Vector3(0, 0, 1);

                   Vector2 texCord = GetTextureCoord(position);

                vertices.Add(new VertexPositionNormalTexture() { Position = position, Normal = normal, TextureCoordinate = texCord });
                runTime++;


                vertexIndices.Add(position, indexCounter++);
            }
        }

        List<int> indices = new List<int>();
        foreach (Vector3 position in originalVertices)
        {
            indices.Add(vertexIndices[position]);
        }

        outVertices = CalculateNormals(vertices.ToArray(), indices.ToArray());
        outIndices = indices.ToArray();
    }

And here comes my version of the "GetTextureCoord" from the forum I mentioned earlier.

    private Vector2 GetTextureCoord(Vector3 normal)
    {
        float targetU;
        float targetV;

        float normalisedX = 0;
        float normalisedZ = -1;
        if (((normal.X * normal.X) + (normal.Z * normal.Z)) > 0)
        {
            normalisedX = (float)Math.Sqrt((normal.X * normal.X) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
            if (normal.X < 0)
            {
                normalisedX = -normalisedX;
            }
            normalisedZ = (float)Math.Sqrt((normal.Z * normal.Z) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
            if (normal.Z < 0)
            {
                normalisedZ = -normalisedZ;
            }
        }
        if (normalisedZ == 0)
        {
            targetU = (float)((normalisedX * Math.PI) / 2);
        }
        else
        {
            targetU = (float)Math.Atan(normalisedX / normalisedZ);
            if (normalisedZ < 0)
            {
                targetU += (float)Math.PI;
            }
        }

        if (targetU < 0)
        {
            targetU += (float)(2 * Math.PI);
        }


        targetU /= (float)(2 * Math.PI);
        targetV = (-normal.Y + 1) / 2;

        return new Vector2 { X = targetU, Y = targetV };
    }

In this forum thread There is apparently two ways it was managed to get solved. One way was to "count the number of faces used for a given level of subdivision, and thus be able to tell when I was outputting a face if it lay on the seam".

The other on was adding this to the "DrawTriangel" code.

point.Set (v1[0], v1[1], v1[2]);
GetTextureCoord (&point, &texU1, &texV);
point.Set (v2[0], v2[1], v2[2]);
GetTextureCoord (&point, &texU2, &texV);
point.Set (v3[0], v3[1], v3[2]);
GetTextureCoord (&point, &texU3, &texV);

if (texU2 - texU1 > 0.2 || texU3 - texU1 > 0.2){
    texAdd1 = 1;
}
if (texU1 - texU2 > 0.2 || texU3 - texU2 > 0.2){
    texAdd2 = 1;
}
if (texU1 - texU3 > 0.2 || texU2 - texU3 > 0.2){
    texAdd3 = 1;
}

And now to my question. Im not really sure how I could apply any of these ideas for solving this issue with the seam that appears.

So, anyone who have an idea how to solve this?

Not sure how I could convert and apply the code mentioned to my Vertices foreach-loop, or how to do this "counting" as mentioned.

EDIT 2012-08-11

OK, Iv updated my entire code to generate the subdivided Icosahedron as it was kind of complex before. It works like before with the seam.

So here it comes. should be easier to try out this code also.

public class Icosahedron2 : Component, I3DComponent
{
    #region   // I3DComponent values
    Vector3 position = Vector3.Zero;
    Matrix rotation = Matrix.Identity;
    Vector3 scale = new Vector3(1, 1, -1);
    BoundingBox boundingBox = new BoundingBox(new Vector3(-1), new Vector3(1));

    public Vector3 Position { get { return position; } set { position = value; } }
    public Vector3 EulerRotation
    {
        get { return MathUtil.MatrixToVector3(Rotation); }
        set { this.Rotation = MathUtil.Vector3ToMatrix(value); }
    }
    public Matrix Rotation { get { return rotation; } set { rotation = value; } }
    public Vector3 Scale { get { return scale; } set { scale = value; } }
    public BoundingBox BoundingBox { get { return boundingBox; } }
    #endregion

    // Effect
    BasicEffect basicEffect;

    // Terrain texture
    Texture2D terrainTexture;


    //Icosahedron variables
    private struct Triangle
    {
        public Vector3 v1;
        public Vector3 v2;
        public Vector3 v3;
        public bool OnSeam;
        public string vectorOnSeam;

        public override string ToString()
        {
            return "On seam:" + OnSeam.ToString() + " " + vectorOnSeam + ", " + v1.ToString() + ", " + v2.ToString() + ", " + v3.ToString();
        }

    }

    List<Triangle> BaseTrangleList = new List<Triangle>();
    List<Triangle> SubTringleList = new List<Triangle>();
    private float Radius;

    Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();    
    List<VertexPositionNormalTexture> vertices = new List<VertexPositionNormalTexture>();
    VertexPositionNormalTexture[] renderVertecis;
    List<int> indices = new List<int>();
    int[] renderIndices;

    #region //Base Indices for Ichsahedron
    /// <summary>
    /// The indices of an icosahedron.
    /// </summary>
    static readonly int[] tindices =
    {  
          1,4,0,     4,9,0,   4,5,9,    8,5,4,     1,8,4,
          1,10,8,   10,3,8,   8,3,5,    3,2,5,     3,7,2,
          3,10,7,   10,6,7,   6,11,7,   6,0,11,    6,1,0,
          10,1,6,   11,0,9,   2,11,9,   5,2,9,     11,2,7
    };
    #endregion

    #region //Base positions for Icosahedron
    /// <summary>
    /// The vertices of an icosahedron.
    /// </summary>
    Vector3[] vdata;

    float X_BASE = 0.525731112119133606f;
    float Z_BASE = 1.0f;

    private void SetBasePositions()
    {
        //  {-X_BASE,0.0,Z_BASE},{X_BASE,0.0,Z_BASE},{-X_BASE,0.0,-Z_BASE},{X_BASE,0.0,-Z_BASE},
        //{0.0,Z_BASE,X_BASE},{0.0,Z_BASE,-X_BASE},{0.0,-Z_BASE,X_BASE},{0.0,-Z_BASE,-X_BASE},
        //{Z_BASE,X_BASE,0.0},{-Z_BASE,X_BASE,0.0},{Z_BASE,-X_BASE,0.0},{-Z_BASE,-X_BASE,0.0}};

        vdata = new Vector3[]
        {              

        Vector3.Normalize(new Vector3(-X_BASE, 0, Z_BASE)), 
       Vector3.Normalize(new Vector3(X_BASE, 0, Z_BASE)), 
       Vector3.Normalize(new Vector3(-X_BASE, 0, -Z_BASE)),
        Vector3.Normalize(new Vector3(X_BASE, 0, -Z_BASE)), 

       Vector3.Normalize(new Vector3(0, Z_BASE, X_BASE)), 
       Vector3.Normalize(new Vector3(0, Z_BASE, -X_BASE)), 
       Vector3.Normalize(new Vector3(0, -Z_BASE, X_BASE)), 
       Vector3.Normalize(new Vector3(0, -Z_BASE, -X_BASE)),

       Vector3.Normalize(new Vector3(Z_BASE, X_BASE, 0)), 
       Vector3.Normalize(new Vector3(-Z_BASE, X_BASE, 0)),  
       Vector3.Normalize(new Vector3(Z_BASE, -X_BASE, 0)),    
       Vector3.Normalize(new Vector3(-Z_BASE, -X_BASE, 0)) 

        };
    }
    #endregion


    #region //Init(float radius, int subdivisions)
    private void Init(float radius, int subdivisions)
    {
        //Builds base triangles
        Radius = radius;
        SetBasePositions();
        for (int i = 0; i < tindices.Length; i += 3)
        {
            Triangle tri = new Triangle()
            {
                v1 = vdata[tindices[i]],
                v2 = vdata[tindices[i + 1]],
                v3 = vdata[tindices[i + 2]],
            };
          BaseTrangleList.Add(tri);
        }

        //Creates the subdevided triangles to make all of them
        foreach (Triangle triangle in BaseTrangleList)
        {
            Subdivide(triangle, subdivisions);
        }

        //checks if triangles are placed on seam
        CheckForSeamPlacement();


        //Creates verticis for all subdevided triangels
        int idCounter = 0;
        foreach(Triangle sub in SubTringleList)
        {
            if (!vertexIndices.ContainsKey(sub.v1))
            {
                Vector3 normal = new Vector3(0, 0, 1);
                Vector2 texCord = GetTextureCoord(sub.v1);
                vertices.Add(new VertexPositionNormalTexture() { Position = sub.v1, Normal = normal, TextureCoordinate = texCord });
                vertexIndices.Add(sub.v1, idCounter++);

                if (sub.vectorOnSeam.Contains("1"))
                {
                    //texCord.X = 1;
                    //Vector3 v12 = sub.v1;
                    //v12.X = 0.0000001f;
                    //vertices.Add(new VertexPositionNormalTexture() { Position = v12, Normal = normal, TextureCoordinate = texCord });
                    //vertexIndices.Add(v12, idCounter++);
                }
            }
            if (!vertexIndices.ContainsKey(sub.v2))
            {
                Vector3 normal = new Vector3(0, 0, 1);
                Vector2 texCord = GetTextureCoord(sub.v2);
                vertices.Add(new VertexPositionNormalTexture() { Position = sub.v2, Normal = normal, TextureCoordinate = texCord });
                vertexIndices.Add(sub.v2, idCounter++);
                if (sub.vectorOnSeam.Contains("2"))
                {
                    //texCord.X = 1;
                    //Vector3 v22 = sub.v2;
                    //v22.X = 0.0000001f;
                    //vertices.Add(new VertexPositionNormalTexture() { Position = v22, Normal = normal, TextureCoordinate = texCord });
                    //vertexIndices.Add(v22, idCounter++);
                }
            }
            if (!vertexIndices.ContainsKey(sub.v3))
            {
                Vector3 normal = new Vector3(0, 0, 1);
                Vector2 texCord = GetTextureCoord(sub.v3);
                vertices.Add(new VertexPositionNormalTexture() { Position = sub.v3, Normal = normal, TextureCoordinate = texCord });
                vertexIndices.Add(sub.v3, idCounter++);
                if (sub.vectorOnSeam.Contains("3"))
                {
                    //texCord.X = 1;
                    //Vector3 v22 = sub.v2;
                    //v22.X = 0.0000001f;
                    //vertices.Add(new VertexPositionNormalTexture() { Position = v22, Normal = normal, TextureCoordinate = texCord });
                    //vertexIndices.Add(v22, idCounter++);
                }
            }
        }

        foreach (Triangle pos in SubTringleList)
        {
            indices.Add(vertexIndices[pos.v1]);               
            indices.Add(vertexIndices[pos.v2]);           
            indices.Add(vertexIndices[pos.v3]);       
        }

        renderIndices = indices.ToArray();

        renderVertecis = CalculateNormals(vertices.ToArray(), indices.ToArray());
        //renderVertecis = vertices.ToArray();
    }
    #endregion

    #region //CheckForSeamPlacement()
    /// <summary>
    /// Checks if any of the triangles are placed on the texture coord 0
    /// </summary>
    private void CheckForSeamPlacement()
    {
        List<Triangle> subSeamCheck = new List<Triangle>();
        foreach (Triangle sub in SubTringleList)
        {
            Triangle tri = sub;
            tri.vectorOnSeam = string.Empty;
            if (GetTextureCoord(tri.v1).X == 0)
            {
                tri.OnSeam = true;
                tri.vectorOnSeam += "1";
            }
            if (GetTextureCoord(tri.v2).X == 0)
            {
                tri.OnSeam = true;
                tri.vectorOnSeam += "2";
            }
            if (GetTextureCoord(tri.v3).X == 0)
            {
                tri.OnSeam = true;
                tri.vectorOnSeam += "3";
            }
            subSeamCheck.Add(tri);
        }
        SubTringleList = subSeamCheck;
    }
    #endregion


    //private bool FoundIndice(Vector3 pos)
    //{
    //    bool contains = false;
    //    foreach (Vector3 indi in vertexIndices)
    //    {
    //        if (indi == pos)
    //        {
    //            contains = true;
    //            break;
    //        }
    //    }
    //    return contains;
    //}


    #region //Vector2 GetTextureCoord(Vector3 normal)
    private Vector2 GetTextureCoord(Vector3 normal)
    {
        float targetU;
        float targetV;

        float normalisedX = 0;
        float normalisedZ = -1;
        if (((normal.X * normal.X) + (normal.Z * normal.Z)) > 0)
        {
            normalisedX = (float)Math.Sqrt((normal.X * normal.X) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
            if (normal.X < 0)
            {
                normalisedX = -normalisedX;
            }
            normalisedZ = (float)Math.Sqrt((normal.Z * normal.Z) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
            if (normal.Z < 0)
            {
                normalisedZ = -normalisedZ;
            }
        }
        if (normalisedZ == 0)
        {
            targetU = (float)((normalisedX * Math.PI) / 2);
        }
        else
        {
            targetU = (float)Math.Atan(normalisedX / normalisedZ);
            if (normalisedZ < 0)
            {
                targetU += (float)Math.PI;
            }
        }

        if (targetU < 0)
        {
            targetU += (float)(2 * Math.PI);
        }


        targetU /= (float)(2 * Math.PI);
        targetV = (-normal.Y + 1) / 2;

        return new Vector2 { X = targetU, Y = targetV };
    }
    #endregion

    #region //Subdivide(Triangle triangle, long subdiv)
    /// <summary>
    /// Devides the triangel according to the subdiv value
    /// </summary>
    /// <param name="triangle"></param>
    /// <param name="subdiv"></param>
    private void Subdivide(Triangle triangle, long subdiv)
    {
        Vector3 v12, v23, v31, v12_2, v23_2, v31_2;

        if (subdiv == 0)
        {
            SubTringleList.Add(triangle);               
            return;
        }

        v12 = triangle.v1 + triangle.v2;
        v23 = triangle.v2 + triangle.v3;
        v31 = triangle.v3 + triangle.v1;


        v12_2 = Vector3.Multiply(Vector3.Normalize(v12),Radius);
        v23_2 = Vector3.Multiply(Vector3.Normalize(v23),Radius);
        v31_2 = Vector3.Multiply( Vector3.Normalize(v31),Radius);

        Triangle t1 = new Triangle { v1 = triangle.v1, v2 = v12_2, v3 = v31_2 };
        Triangle t2 = new Triangle { v1 = triangle.v2, v2 = v23_2, v3 = v12_2 };
        Triangle t3 = new Triangle { v1 = triangle.v3, v2 = v31_2, v3 = v23_2 };
        Triangle t4 = new Triangle { v1 = v12_2, v2 = v23_2, v3 = v31_2 };


        Subdivide( t1, subdiv - 1);
        Subdivide( t2, subdiv - 1);
        Subdivide( t3, subdiv - 1);
        Subdivide( t4, subdiv - 1);
    }
    #endregion

    #region //CalculateNormals()
    /// <summary>
    /// Calculates Noramals for vertices
    /// </summary>
    private static VertexPositionNormalTexture[] CalculateNormals(VertexPositionNormalTexture[] vertices, int[] indices)
    {

        for (int i = 0; i < vertices.Length; i++)
            vertices[i].Normal = new Vector3(0, 0, 0);

        for (int i = 0; i < indices.Length / 3; i++)
        {
            int index1 = indices[i * 3];
            int index2 = indices[i * 3 + 1];
            int index3 = indices[i * 3 + 2];

            Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
            Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
            Vector3 normal = Vector3.Cross(side1, side2);

            vertices[index1].Normal += normal;
            vertices[index2].Normal += normal;
            vertices[index3].Normal += normal;
        }

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i].Normal.Normalize();
        }
        return vertices;
    }
    #endregion


    public Icosahedron2(float radius, int subdevisions)
        : base()
    {
        basicEffect = new BasicEffect(Engine.GraphicsDevice);
        SetupEffect();
        Radius = radius;
        Init(radius, subdevisions);
    }
    public Icosahedron2(float radius, int subdevisions, Texture2D Texture)
        : base()
    {
        basicEffect = new BasicEffect(Engine.GraphicsDevice);
        terrainTexture = Texture;
        SetupEffect();        
        Radius = radius;
        Init(radius, subdevisions);
    }

    #region// Setup BasicEffect
    /// <summary>
    /// Setsup basic effect parameters
    /// </summary>
    private void SetupEffect()
    {
        basicEffect.Texture = terrainTexture;
        basicEffect.TextureEnabled = true;
        //basicEffect.VertexColorEnabled = false;
        //basicEffect.EnableDefaultLighting();
        //basicEffect.DirectionalLight0.Direction = new Vector3(1, -1, 1);
        //basicEffect.DirectionalLight0.Enabled = true;
        //basicEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
        //basicEffect.DirectionalLight1.Enabled = false;
        //basicEffect.DirectionalLight2.Enabled = false;
        basicEffect.SpecularColor = new Vector3(0, 0, 0);
    }
    #endregion

    #region //Draw
    /// <summary>
    /// Draw object
    /// </summary>
    public override void Draw()
    {
        // Look for a camera in the service container
        Camera camera = Engine.Services.GetService<Camera>();
        // Throw an exception if one isn't present
        if (camera == null)
        {
            throw new Exception("Camera not found in engine's"
            + "service container, cannot draw");
        }

        // Set effect values
        basicEffect.World = MathUtil.CreateWorldMatrix(position, rotation, scale);
        basicEffect.View = camera.View;
        basicEffect.Projection = camera.Projection;

        //RasterizerState rs = new RasterizerState();
        //rs.CullMode = CullMode.None;
        //rs.FillMode = FillMode.WireFrame;
        //Engine.GraphicsDevice.RasterizerState = rs;   

        Engine.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
        //Engine.GraphicsDevice.BlendState = BlendState.AlphaBlend;
        //Engine.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;


        foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        {
            //VertexPositionNormalTexture
            pass.Apply();
            Engine.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, renderVertecis, 0, renderVertecis.Length, renderIndices, 0, renderIndices.Length / 3, VertexPositionNormalTexture.VertexDeclaration);
        }
    }
    #endregion
}

Final update 2012-8-12

Thanks to David Lively this problem has finally be solved. Using this code like this in the updated code will solve the problem.

 RepairTextureWrapSeam(vertices, indices);

 renderIndices = indices.ToArray();
 renderVertecis = CalculateNormals(vertices.ToArray(), indices.ToArray());
\$\endgroup\$
9
  • \$\begingroup\$ An image is worth a thousand words they say and while I'm sure I could find the detail of the seam by researching the other posts or your code, simply adding an image demonstrating the seam would make it a lot easier for anyone to help you. \$\endgroup\$ Commented Aug 5, 2012 at 16:29
  • \$\begingroup\$ Ok, have added an Image of what is happening. \$\endgroup\$ Commented Aug 5, 2012 at 17:39
  • \$\begingroup\$ Added a closeup picture to to maybe show a bit better what happends. \$\endgroup\$ Commented Aug 5, 2012 at 18:55
  • \$\begingroup\$ You need to duplicate vertices along the seam (if you are not already). \$\endgroup\$ Commented Aug 6, 2012 at 7:36
  • \$\begingroup\$ No, I dont have any duplicated vertices so far. Ill see if I can give it a go. \$\endgroup\$ Commented Aug 6, 2012 at 11:21

3 Answers 3

4
\$\begingroup\$

Here's a method I wrote about a year ago to fix this.

Essentially, it looks for triangles that have UV coordinates that appear in the wrong winding order, which should only happen when a face would span a texture edge. For instance, an edge (v0,v1) with UV coordinates (0.99,0),(0.01,0). Your seam appears because the rasterizer doesn't realize that you only want the far right and left edge - it interpolates from 0.99 down to 0.01, which causes your entire texture width to be squeezed into that face.

Call this method after you've completely generated your sphere. (Alternatively, you could examine the UV order as the sphere is generated, or even before subdivision, but that's another topic.)

This method tracks the number of changes that are made during the repair; the idea being that, if it's a LOT relative to the number of vertices in your model, something may be amiss.

I seem to recall writing this at about 4 AM before a presentation, so it's probably not the quickest or cleanest implementation, but the approach is sound and should work for any closed polyhedron.

    /// <summary>
    /// split vertices for faces that incorrectly span an edge of the texture
    /// </summary>
    /// <param name="vertices"></param>
    /// <param name="indices"></param>
    public static void RepairTextureWrapSeam(List<VertexPositionNormalTexture> vertices, List<int> indices)
    {
        var newIndices = new List<int>();

        var corrections = 0;

        /// whenever a vertex is split, add its original and new indices to the dictionary to avoid
        /// creating duplicates.
        var correctionList = new Dictionary<int, int>();

        for (var i = indices.Count - 3; i >= 0; i -= 3)
        {
            /// see if the texture coordinates appear in counter-clockwise order.
            /// If so, the triangle needs to be rectified.
            var v0 = new Vector3(vertices[indices[i + 0]].TextureCoordinate, 0);
            var v1 = new Vector3(vertices[indices[i + 1]].TextureCoordinate, 0);
            var v2 = new Vector3(vertices[indices[i + 2]].TextureCoordinate, 0);

            var cross = Vector3.Cross(v0 - v1, v2 - v1);

            if (cross.Z <= 0)
            {
                /// this should only happen if the face crosses a texture boundary

                var corrected = false;

                for (var j = i; j < i + 3; j++)
                {
                    var index = indices[j];

                    var vertex = vertices[index];
                    /// 0.9 UV fudge factor - should be able to get rid of this when I get more sleep
                    if (vertex.TextureCoordinate.X >= 0.9f)
                    {
                        /// need to correct this vertex.
                        if (correctionList.ContainsKey(index))
                            newIndices.Add(correctionList[index]);
                        else
                        {
                            var texCoord = vertex.TextureCoordinate;

                            texCoord.X -= 1;
                            vertex.TextureCoordinate = texCoord;
                            corrected = true;

                            vertices.Add(vertex);

                            var correctedVertexIndex = vertices.Count - 1;

                            correctionList.Add(index, correctedVertexIndex);

                            newIndices.Add(correctedVertexIndex);
                        }
                    }
                    else
                        newIndices.Add(index);

                }

                if (corrected)
                    corrections++;
            }
            else
                newIndices.AddRange(indices.GetRange(i, 3));
        }

        Debug.WriteLine("Corrected {0} of {1}", corrections, newIndices.Count / 3);


        indices.Clear();
        indices.AddRange(newIndices);
    }
\$\endgroup\$
3
  • 1
    \$\begingroup\$ It works! Excellent my friend! I put it just before my to lines ' renderIndices = indices.ToArray(); renderVertecis = CalculateNormals(vertices.ToArray(), indices.ToArray());' \$\endgroup\$ Commented Aug 12, 2012 at 8:54
  • \$\begingroup\$ Ok. I'll rearrange the code to avoid arrays. Thanks for the tip. \$\endgroup\$ Commented Aug 13, 2012 at 7:17
  • \$\begingroup\$ Update: my comment about array parameters was incorrect. I've removed it. \$\endgroup\$
    – 3Dave
    Commented Feb 18, 2020 at 18:01
4
\$\begingroup\$

To texture map a model you supply a 2D coordinate for each vertice, this coordinate is used to map the texture (2D) to the model (3D). This works fine for most models, but when your texturing a continous spherical object (such as a model of earth) you will at some point have a vertice where one of the coordinates is at the highest (X or Y value), and the next one is at its lowest. At this point the graphics card won't wrap around the texture the short way, instead it will go back over the visible texture, thus reflecting the texture and rendering the whole texture, causing a visible seam.

To fix this, as mentioned in the comments, you need to duplicate the vertices where the texture coordinate is at 0 (for one of the directions, X in this example), the duplicate vertice needs to have the texture coordinate set to 1, this allows the graphics card to understand that it should continue in a left to right direction in the texture, rather than going right to left.

\$\endgroup\$
3
  • \$\begingroup\$ Ok, sounds like a good explanation. Thank you for it. I'll see if I can make some code to deal with this now then I know what too look for. \$\endgroup\$ Commented Aug 6, 2012 at 16:27
  • \$\begingroup\$ I can find a few vertices that have X= 0 as you say. Duplicate these and set there values X to 1. It gets real messy. Probably need to update the Indecies list to. Any idea about how this should be updated as the vertice at current point has been duplicated? \$\endgroup\$ Commented Aug 6, 2012 at 18:51
  • \$\begingroup\$ Decent explanation, but in floating point "0" and "1" aren't the immutable concepts we'd like them to be. \$\endgroup\$
    – 3Dave
    Commented Aug 13, 2012 at 4:54
4
\$\begingroup\$

I've been strugling with issue myself, and finally made it work. First of all, I learned that the octahedron should have the right orientation, because then you won't have to split any triangles. This is hard for me to explain, so here a picture for clarity:

http://i.imgur.com/4Ip4C.jpg

In this picture, I've highlighted the vertices with red, where the texture U coordinate is 0. As you can see, (A) produces the zig-zag-like distortion, while (B) produces only vertical distortion. You want the orientation in (B), as this will save you some trouble. You can simply do this by modifying the starting configuration for the vertices of the octahedron you're subdiving.

My approach has been diffrent from yours, as I work with Points (Vector3's) and Triangles (collection of 3 indices), and then convert them to proper Index and Vertex objects once I'm done modifying them. This turned out to be usefull when correcting this issue.

Now, to get rid of the distorion follow theese steps:

  1. Check each triangle if it is on the seam.

    1a. Get each texture coord for the triangle.

    1b. If one or two have their U coord = 0, it is on the seam

    1c. If the remaining texcoords have U > 0.5 (closer to 1 than 0) this triangle is also causing distortion.

  2. If so, clone the vertices where texcoord.U = 0, and set the U value to 1.
  3. Get the index of each cloned vertex
  4. Alter the current triangle, to use theese indices instead.
  5. Draw/Add the altered triangle

This approach will only work, if you have the right orintation of the octahderon as I mentioned earlier. I can't quite tell you how to implement this solution in your approach, but I hope this information can help you.

Edit: Btw, you can use this method for getting texture coordinates instead:

    private Vector2 MapUv(Vector3 p)
    {
        float u = 0.5f - ((float)Math.Atan2(p.Z, p.X) / MathHelper.TwoPi);
        float v = 0.5f - 2.0f * ((float)Math.Asin(p.Y) / MathHelper.TwoPi);

        return new Vector2(u, v);
    }

Edit (2012-08-12)

I've have cleaned and commented on my working solution - It's far from optimized at this point, but it gets the job done though.

It's quite a lot of code with the comments and all, so I've put it at http://pastebin.com/E6qG234u

\$\endgroup\$
4
  • \$\begingroup\$ Thank you, will sure try this out. By the way, when you talk about altering you triangles. Do you have all uniqe vertices like I do with ' Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();' ? Just wondering. \$\endgroup\$ Commented Aug 9, 2012 at 16:59
  • \$\begingroup\$ Yes, I have unique vertices. I have a List<Vector3> for my points, and a List<Face> for my faces. My "Face" class is just simply a collection of 3 integers, representing how to connect the Vector3's in my list. \$\endgroup\$
    – MxrFxr
    Commented Aug 9, 2012 at 18:24
  • \$\begingroup\$ This MapUV dose not work that well with this type of shape. I sure get a better result with the GetTextureCoord I have set up. \$\endgroup\$ Commented Aug 11, 2012 at 19:14
  • \$\begingroup\$ Note that Asin() will only behave as expected in this case if p is normalized. \$\endgroup\$
    – 3Dave
    Commented Jan 25, 2017 at 13:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .