UV球体 - 摆脱连接/顶点"点"

时间:2018-03-28 07:43:56

标签: c++ opengl opengl-es-2.0 fragment-shader vertex-shader

我正在创建一个UV球体(类似于划分为纬线的地球球体)。我这样做是通过:

  1. 计算每个平行纬度圆周围的所有顶点(例如每个圆圈72个点)
  2. 使用GL_TRIANGLE_STRIP填写每个"切片"在每个纬度圈之间。
  3. 不幸的是,我一直在看到完美球体上的圆点。

    enter image description here

    导致这种情况的原因是什么?如何摆脱它?

    void CSphere2::AddVertices( void )
    {
      #define SPHERE2_RES 72
    
      // Create sphere using horizontal slices/circles
      int nPointsPerCircle = SPHERE2_RES;
      int nStackedCircles  = SPHERE2_RES;
    
      GLfloat r          = m_Size;
      GLfloat yAngle     = - (PI / 2.0f);  // Start at -90deg and work up to +90deg (south to north pole)
      GLfloat yAngleStep = PI / nStackedCircles;
    
      // Sweep angle is zero initially for pointing towards me (-Z direction)
      GLfloat horizSweepAngle = 0;
      GLfloat horizSweepStep  = ( 2 * PI ) / nPointsPerCircle;
    
      // Each time we have a slice, the top and bottom radii vary..
      GLfloat sweepRadiusTop;
      GLfloat sweepRadiusBottom;
    
      GLfloat xBottomPoint;
      GLfloat zBottomPoint;
    
      GLfloat xTopPoint;
      GLfloat zTopPoint;
    
      for( int c = 0; c < nStackedCircles; c ++ )
      {
        // Draw a circle - note that this always uses two circles - a top and bottom circle.
        GLfloat yBottomCircle;
        GLfloat yTopCircle;
    
        yTopCircle    = r * sin( yAngle + yAngleStep );
        yBottomCircle = r * sin( yAngle );
    
        std::vector<GLfloat> vBottom_x;
        std::vector<GLfloat> vBottom_z;
    
        std::vector<GLfloat> vTop_x;
        std::vector<GLfloat> vTop_z;
    
        sweepRadiusTop    = r * cos( yAngle + yAngleStep );
        sweepRadiusBottom = r * cos( yAngle );
    
        // Add 1 face - a triangle strip per slice..
        AddFace();
    
        m_Faces[ c ].m_DrawType = GL_TRIANGLE_STRIP;
    
        // Now work out the position of the points around each circle - bottom points will always be the
        //      same as the last top circle points.. but I'm not going to try optimising yet..
        for( int s = 0; s < nPointsPerCircle; s ++ )
        {
          GLfloat xBottomPoint = sweepRadiusBottom * sin( horizSweepAngle );
          GLfloat zBottomPoint = sweepRadiusBottom * cos( horizSweepAngle );
    
          GLfloat xTopPoint = sweepRadiusTop * sin( horizSweepAngle + horizSweepStep );
          GLfloat zTopPoint = sweepRadiusTop * cos( horizSweepAngle + horizSweepStep );
    
          vBottom_x.push_back( xBottomPoint );
          vBottom_z.push_back( zBottomPoint );
    
          vTop_x.push_back( xTopPoint );
          vTop_z.push_back( zTopPoint );
    
          horizSweepAngle += horizSweepStep;
        }
    
        // OPTIMISE THIS!!
        for( int s = 1; s <= nPointsPerCircle + 1; s ++ )
        {
          if( s == nPointsPerCircle + 1 )
          {
            // Join the last bottom point with the very first top point - go one more to fully close and leave no vertical gap
            xTopPoint = vTop_x[ 1 ];
            zTopPoint = vTop_z[ 1 ];
    
            xBottomPoint = vBottom_x[ 0 ];
            zBottomPoint = vBottom_z[ 0 ];
          }
          else
          if( s == nPointsPerCircle )
          {
            // Join the last bottom point with the very first top point
            xTopPoint = vTop_x[ 0 ];
            zTopPoint = vTop_z[ 0 ];
    
            xBottomPoint = vBottom_x[ s - 1 ];
            zBottomPoint = vBottom_z[ s - 1 ];
          }
          else
          {
            xTopPoint = vTop_x[ s ];
            zTopPoint = vTop_z[ s ];
    
            xBottomPoint = vBottom_x[ s - 1 ];
            zBottomPoint = vBottom_z[ s - 1 ];
          }
    
          // Calculate and add the Normal for each vertex.. Normal for a point on surface of a Sphere2 should be the unit vector going from centre
          //      of the Sphere2 to the surface (x,y,z).
          //
          //      If centre of Sphere2 is 0,0,0 then N = | {x,y,z} - {0,0,0} | = | {x,y,z} |
          glm::vec3 vNormalBottom = glm::vec3( xBottomPoint, yBottomCircle, zBottomPoint );
          vNormalBottom = glm::normalize( vNormalBottom );
    
          glm::vec3 vNormalTop = glm::vec3( xTopPoint, yTopCircle, zTopPoint );
          vNormalTop = glm::normalize( vNormalTop );
    
          // Add bottom of slice vertex..
          m_Faces[ c ].AddVertexWithNormal( xBottomPoint, yBottomCircle, zBottomPoint, vNormalBottom.x, vNormalBottom.y, vNormalBottom.z );
    
          // Add top of slice vertex, next step position..
          m_Faces[ c ].AddVertexWithNormal( xTopPoint, yTopCircle, zTopPoint, vNormalTop.x, vNormalTop.y, vNormalTop.z );
        }
    
        int nVertexCount = m_Faces[ c ].m_Vertices.size();
    
        m_Faces[ c ].m_SideCount = nVertexCount;
    
        // Face colouring colours the vertices so they need to be created first..
        m_Faces[ c ].SetRGB( m_RGBA.r, m_RGBA.g, m_RGBA.b );
    
        yAngle += yAngleStep;
      }
    }
    
    void CSphere2::Create( GLfloat fSize )
    {
      m_Size = fSize;
    
      // Must add vertices first..
      AddVertices();
    
      glGenBuffers( 1, &m_VBO );
      glBindBuffer( GL_ARRAY_BUFFER, m_VBO );
    
      int nFaces = m_Faces.size();
      int nVertexCount = 0;
    
      for( int f = 0; f < nFaces; f ++ )
      {
        nVertexCount += m_Faces[ f ].m_Vertices.size();
        m_Faces[ f ].m_SideCount = nVertexCount;
      }
    
      // Define the size of the buffer.. 
      glBufferData( GL_ARRAY_BUFFER, sizeof( COLVERTEX ) * nVertexCount, NULL, GL_STATIC_DRAW );
    
      int nOffset = 0;
    
      for( int f = 0; f < nFaces; f ++ )
      {
        // Copy in each vertice's data..
        for( int v = 0; v < (int) m_Faces[ f ].m_Vertices.size(); v ++ )
        {
          glBufferSubData( GL_ARRAY_BUFFER, nOffset, sizeof( COLVERTEX ), &m_Faces[ f ].m_Vertices[ v ].m_VertexData );
    
          nOffset += sizeof( COLVERTEX );
        }
      }
    
      glBindBuffer( GL_ARRAY_BUFFER, 0 );
    }
    

    我从其他地方复制的其他例子也遇到了同样的问题,所以我坐下来,自己做了数学计算,但仍然遇到同样的问题。

    顶点着色器:

    char *vs3DShader  = 
    
    "#version 140\n"
    
    "#extension GL_ARB_explicit_attrib_location : enable\n"
    
    "layout (location = 0) in vec3 Position;"
    "layout (location = 1) in vec4 color;"
    "layout (location = 2) in vec3 aNormal;"
    
    "out vec4 frag_color;"
    "out vec3 Normal;"
    "out vec3 FragPos;"
    
    "uniform mat4 model;"
    "uniform mat4 view;"
    "uniform mat4 projection;"
    
    "void main()"
    "{"
    "  FragPos = vec3(model * vec4(Position, 1.0));"
    
    "  gl_Position = projection * view * vec4(FragPos, 1.0);"
    
    //  Rotate normals with respect to current Model matrix (object rotation).
    "  Normal = mat3( transpose( inverse( model ) ) ) * aNormal; "
    
    "  // Pass vertex color to fragment shader.. \n"
    "  frag_color = color;"
    "}"
    ;
    

    片段着色器:

    char *fs3DShader  = 
    
    "#version 140\n"
    "in  vec4 frag_color;"
    "in  vec3 Normal;"
    "in  vec3 FragPos;"
    
    "out vec4 FragColor;"
    
    "uniform vec3 lightPos; "
    "uniform vec3 lightColor; "
    
    "void main()"
    "{"
    "  // ambient\n"
    "  float ambientStrength = 0.1;"
    "  vec3 ambient = ambientStrength * lightColor;"
    
    "  // diffuse \n"
    "  vec3 norm = normalize(Normal);"
    "  vec3 lightDir = normalize(lightPos - FragPos);"
    "  float diff = max(dot(norm, lightDir), 0.0);"
    "  vec3 diffuse = diff * lightColor;"
    
    "  vec3 result = (ambient + diffuse) * frag_color;"
    
    "  FragColor = vec4(result, 1.0);"
    "}"                         
    ;
    

    我错过了某种平滑选项吗?我已经尝试将我的观点移动到球体的两侧,并且点周围都在发生 - 所以它不是三角形条带和#34;关闭的地方。这就是问题所在 - 它遍布整个领域。

    见下面的亮点:

    enter image description here

    更新:我只是想证明回归零度不是问题所在。下面是每个圆圈的四分之一扫过90度时的图像。点仍然出现在中间区域。

    enter image description here

2 个答案:

答案 0 :(得分:3)

浮点精度不是无限的,当使用超越数时,你将不可避免地积累错误。

这是一个示例程序,它执行与程序相同的循环,除了它只打印出最终角度:

#include <cmath>
#include <cstdio>

int main() {
    const int N = 72;
    const float step = std::atan(1.0f) * 8 / N;
    float x = 0.0f;
    for (int i = 0; i < N; i++) {
        x += step;
    }
    std::printf("x - 2pi = %f\n", x - 8 * std::atan(1.0f));
    return 0;
}

在我的系统上,它打印出-0.000001。接近零,但不是零。

如果您希望网格中的两个点对齐,请不要给它们不同的值。否则你会得到这样的小缝。

解决此问题的典型方法是生成如下的圆圈:

#include <cmath>
#include <cstdio>
#include <vector>

struct vec2 { float x, y; };

int main() {
    const int N = 72;
    const float step = std::atan(1.0f) * 8 / N;
    std::vector<vec2> circle;
    for (int i = 0; i < N; i++) {
        float a = i * step;
        circle.push_back({ std::cos(a), std::sin(a) });
    }
    return 0;
}

在圈子的每个点circle[i],下一个点现在只是circle[(i+1)%N]。这可确保circle[N-1]之后的点始终与circle[0]完全相同。

答案 1 :(得分:0)

我在问题中发现了顶点计算的几个问题。由于每次扫描水平切片时都在计算底部和顶部顶点,因此产生了舍入/精度误差。当前切片顶部的一个点应该与下一个切片的底部点相同 - 但是我正在计算这个顶部和底部,如Dietrich Epp建议的那样递增。这导致了不同的价值观。我的解决方案是重复使用前面的顶部圆顶点作为下一个切片的底部顶点。

我也没有使用相同的扫掠角度计算顶部和底部圆圈的x / z位置 - 我增加了我不应该做的角度。

所以从根本上说,问题是由2个重叠的顶点引起的,这些顶点应该具有相同的坐标,但是它们之间的差别很小。

这是工作解决方案:

void CSphere2::AddVertices( void )
{
  #define SPHERE2_RES 72

  // Create sphere using horizontal slices/circles
  int nPointsPerCircle = SPHERE2_RES;
  int nStackedCircles  = SPHERE2_RES;

  GLfloat r          = m_Size;
  GLfloat yAngle     = - (PI / 2.0f);  // Start at -90deg and work up to +90deg (south to north pole)
  GLfloat yAngleStep = PI / nStackedCircles;

  // Sweep angle is zero initially for pointing towards me (-Z direction)
  GLfloat horizSweepAngle = 0;
  GLfloat horizSweepStep  = ( 2 * PI ) / nPointsPerCircle;

  // Each time we have a slice, the top and bottom radii vary..
  GLfloat sweepRadiusTop;
  GLfloat sweepRadiusBottom;

  GLfloat xBottomPoint;
  GLfloat zBottomPoint;

  GLfloat xTopPoint;
  GLfloat zTopPoint;

  std::vector<GLfloat> vCircle_x;
  std::vector<GLfloat> vCircle_z;

  std::vector<GLfloat> vLastCircle_x;
  std::vector<GLfloat> vLastCircle_z;

  int nFace = 0;

  for( int c = 0; c <= nStackedCircles + 1; c ++ )
  {
    // Draw a circle - note that this always uses two circles - a top and bottom circle.
    GLfloat yBottomCircle;
    GLfloat yTopCircle;

    yTopCircle    = r * sin( yAngle + yAngleStep );
    yBottomCircle = r * sin( yAngle );

    sweepRadiusTop = r * cos( yAngle );

    GLfloat xCirclePoint;
    GLfloat zCirclePoint;

    horizSweepAngle = 0;

    vCircle_x.clear();
    vCircle_z.clear();

    // Now work out the position of the points around each circle - bottom points will always be the
    //      same as the last top circle points.. 
    for( int s = 0; s < nPointsPerCircle; s ++ )
    {
      zCirclePoint = sweepRadiusTop * sin( horizSweepAngle );
      xCirclePoint = sweepRadiusTop * cos( horizSweepAngle );

      vCircle_x.push_back( xCirclePoint );
      vCircle_z.push_back( zCirclePoint );

      horizSweepAngle += horizSweepStep;
    }

    if( c == 0 )
    {
      // First time around there is no last circle, so just use the same points..
      vLastCircle_x = vCircle_x;
      vLastCircle_z = vCircle_z;

      // And don't add vertices until next time..
      continue;
    }

    // Add 1 face - a triangle strip per slice..
    AddFace();

    m_Faces[ nFace ].m_DrawType = GL_TRIANGLE_STRIP;

    for( int s = 1; s <= nPointsPerCircle + 1; s ++ )
    {
      if( s == nPointsPerCircle + 1 )
      {
        // Join the last bottom point with the very first top point
        xTopPoint = vCircle_x[ 1 ];
        zTopPoint = vCircle_z[ 1 ];

        xBottomPoint = vLastCircle_x[ 0 ];
        zBottomPoint = vLastCircle_z[ 0 ];
      }
      else
      if( s == nPointsPerCircle )
      {
        // Join the last bottom point with the very first top point
        xTopPoint = vCircle_x[ 0 ];
        zTopPoint = vCircle_z[ 0 ];

        xBottomPoint = vLastCircle_x[ s - 1 ];
        zBottomPoint = vLastCircle_z[ s - 1 ];
      }
      else
      {
        xTopPoint = vCircle_x[ s ];
        zTopPoint = vCircle_z[ s ];

        xBottomPoint = vLastCircle_x[ s - 1 ];
        zBottomPoint = vLastCircle_z[ s - 1 ];
      }

      // Calculate and add the Normal for each vertex.. Normal for a point on surface of a Sphere2 should be the unit vector going from centre
      //      of the Sphere2 to the surface (x,y,z).
      //
      //      If centre of Sphere2 is 0,0,0 then N = | {x,y,z} - {0,0,0} | = | {x,y,z} |
      glm::vec3 vNormalBottom = glm::vec3( xBottomPoint, yBottomCircle, zBottomPoint );
      vNormalBottom = glm::normalize( vNormalBottom );

      glm::vec3 vNormalTop = glm::vec3( xTopPoint, yTopCircle, zTopPoint );
      vNormalTop = glm::normalize( vNormalTop );

      // Add bottom of slice vertex..
      m_Faces[ nFace ].AddVertexWithNormal( xBottomPoint, yBottomCircle, zBottomPoint, vNormalBottom.x, vNormalBottom.y, vNormalBottom.z );

      // Add top of slice vertex, next step position..
      m_Faces[ nFace ].AddVertexWithNormal( xTopPoint, yTopCircle, zTopPoint, vNormalTop.x, vNormalTop.y, vNormalTop.z );
    }

    // Now copy the current circle x/y positions as the last circle positions (bottom circle)..
    vLastCircle_x = vCircle_x;
    vLastCircle_z = vCircle_z;

    int nVertexCount = m_Faces[ nFace ].m_Vertices.size();

    m_Faces[ nFace ].m_SideCount = nVertexCount;

    // Face colouring colours the vertices so they need to be created first..
    m_Faces[ nFace ].SetRGB( m_RGBA.r, m_RGBA.g, m_RGBA.b );

    yAngle += yAngleStep;

    nFace ++;
  }
}