【问题标题】:OpenGL Sphere vertices and UV coordinatesOpenGL Sphere 顶点和 UV 坐标
【发布时间】:2017-01-09 20:59:09
【问题描述】:

我知道这个问题有很多类似的问题,例如this one,但我似乎无法弄清楚我的程序出了什么问题。

我正在尝试使用简单的经度/纬度方法创建一个单位球体,然后我尝试使用 UV 坐标将纹理包裹在球体周围。

我看到了经典的垂直接缝问题,但我在两极也有些奇怪。

北极...

南极...

缝...

图像来自一个有 180 个堆栈和 360 个切片的球体。

我如下创建它。

首先,这里有几个我正在使用的便利结构...

struct Point {
    float x;
    float y;
    float z;
    float u;
    float v;
};

struct Quad {
    Point lower_left;  // Lower left corner of quad
    Point lower_right; // Lower right corner of quad
    Point upper_left;  // Upper left corner of quad
    Point upper_right; // Upper right corner of quad
};

我首先指定一个“_stacks”高、“_slices”宽的球体。

float* Sphere::generate_glTriangle_array(int& num_elements) const
{
    int elements_per_point  = 5; //xyzuv
    int points_per_triangle = 3;
    int triangles_per_mesh = _stacks * _slices * 2; // 2 triangles makes a quad
    num_elements = triangles_per_mesh * points_per_triangle * elements_per_point;

    float *buff = new float[num_elements];
    int i = 0;

    Quad q;

    for (int stack=0; stack<_stacks; ++stack)
    {
        for (int slice=0; slice<_slices; ++slice)
        {
            q = generate_sphere_quad(stack, slice);
            load_quad_into_array(q, buff, i);
        }
    }

    return buff;
}

Quad Sphere::generate_sphere_quad(int stack, int slice) const
{
    Quad q;

    std::cout << "Stack " << stack << ", Slice: " << slice << std::endl;

    std::cout << "   Lower left...";
    q.lower_left = generate_sphere_coord(stack, slice);
    std::cout << "   Lower right...";
    q.lower_right = generate_sphere_coord(stack, slice+1);
    std::cout << "   Upper left...";
    q.upper_left = generate_sphere_coord(stack+1, slice);
    std::cout << "   Upper right...";
    q.upper_right = generate_sphere_coord(stack+1, slice+1);
    std::cout << std::endl;

    return q;
}

Point Sphere::generate_sphere_coord(int stack, int slice) const
{
    Point p;

    p.y = 2.0 * stack / _stacks - 1.0;

    float r = sqrt(1 - p.y * p.y);
    float angle = 2.0 * M_PI * slice / _slices;

    p.x = r * sin(angle);
    p.z = r * cos(angle);

    p.u = (0.5 + ( (atan2(p.z, p.x)) / (2 * M_PI) ));
    p.v = (0.5 + ( (asin(p.y)) / M_PI ));

    std::cout << " Point: (x: " << p.x << ", y: " << p.y << ", z: " << p.z << ") [u: " << p.u << ", v: " << p.v << "]" << std::endl;

    return p;
}

然后我加载我的数组,为每个 Quad 指定两个逆时针三角形的顶点...

void Sphere::load_quad_into_array(const Quad& q, float* buff, int& buff_idx, bool counter_clockwise=true)
{
    if (counter_clockwise)
    {
        // First triangle
        load_point_into_array(q.lower_left, buff, buff_idx);
        load_point_into_array(q.upper_right, buff, buff_idx);
        load_point_into_array(q.upper_left, buff, buff_idx);

        // Second triangle
        load_point_into_array(q.lower_left, buff, buff_idx);
        load_point_into_array(q.lower_right, buff, buff_idx);
        load_point_into_array(q.upper_right, buff, buff_idx);
    }
    else
    {
        // First triangle
        load_point_into_array(q.lower_left, buff, buff_idx);
        load_point_into_array(q.upper_left, buff, buff_idx);
        load_point_into_array(q.upper_right, buff, buff_idx);

        // Second triangle
        load_point_into_array(q.lower_left, buff, buff_idx);
        load_point_into_array(q.upper_right, buff, buff_idx);
        load_point_into_array(q.lower_right, buff, buff_idx);
    }
}

void Sphere::load_point_into_array(const Point& p, float* buff, int& buff_idx)
{
    buff[buff_idx++] = p.x;
    buff[buff_idx++] = p.y;
    buff[buff_idx++] = p.z;
    buff[buff_idx++] = p.u;
    buff[buff_idx++] = p.v;
}

我的顶点和片段着色器很简单...

// Vertex shader
#version 450 core

in vec3 vert;
in vec2 texcoord;

uniform mat4 matrix;

out FS_INPUTS {
   vec2 i_texcoord;
} tex_data;

void main(void) {
   tex_data.i_texcoord = texcoord;
   gl_Position = matrix * vec4(vert, 1.0);
}

// Fragment shader
#version 450 core

in FS_INPUTS {
   vec2 i_texcoord;
};

layout (binding=1) uniform sampler2D tex_id;

out vec4 color;

void main(void) {
   color = texture(tex_id, texcoord);
}

我的绘图命令是:

glDrawArrays(GL_TRIANGLES, 0, num_elements/5);

谢谢!

【问题讨论】:

  • 看看这个:Applying map of the earth texture a Sphere 懒得分析你的代码,但可能的原因是:使用GL_CLAMP 而不是GL_CLAMP_TO_EDGE 或者在某处搞砸了坐标。
  • 谢谢,但我正在使用 GL_CLAMP_TO_EDGE。我不断检查我打印的 XYZ 和 UV 坐标,但据我所知,它们似乎是正确的。

标签: c++ opengl


【解决方案1】:

首先,这段代码做了一些有趣的额外工作:

Point Sphere::generate_sphere_coord(int stack, int slice) const
{
    Point p;

    p.y = 2.0 * stack / _stacks - 1.0;

    float r = sqrt(1 - p.y * p.y);
    float angle = 2.0 * M_PI * slice / _slices;

    p.x = r * sin(angle);
    p.z = r * cos(angle);

    p.u = (0.5 + ( (atan2(p.z, p.x)) / (2 * M_PI) ));
    p.v = (0.5 + ( (asin(p.y)) / M_PI ));

    return p;
}

调用cossin 只是在结果上调用atan2 在最好的情况下只是额外的工作,在最坏的情况下你可能会得到错误的分支削减。您可以直接从sliceslice 计算p.u

接缝

你将在你的球体中有一个接缝。这是正常的,大多数模型的 UV 贴图中某处都会有一个接缝(或多个接缝)。问题是 UV 坐标在接缝附近仍应线性增加。例如,考虑一个环绕地球赤道的顶点循环。在某些时候,UV 坐标会环绕,如下所示:

0.8, 0.9, 0.0, 0.1, 0.2

问题是你会得到四个四边形,但其中一个是错误的:

quad 1: u = 0.8 ... 0.9
quad 2: u = 0.9 ... 0.0 <<----
quad 3: u = 0.0 ... 0.1
quad 4: u = 0.1 ... 0.2

看看quad 2 有多乱。您将不得不生成以下数据:

quad 1: u = 0.8 ... 0.9
quad 2: u = 0.9 ... 1.0
quad 3: u = 0.0 ... 0.1
quad 4: u = 0.1 ... 0.2

固定版本

这是一个固定版本的草图。

namespace {

const float pi = std::atan(1.0f) * 4.0f;

// Generate point from the u, v coordinates in (0..1, 0..1)
Point sphere_point(float u, float v) {
    float r = std::sin(pi * v);
    return Point{
        r * std::cos(2.0f * pi * u),
        r * std::sin(2.0f * pi * u),
        std::cos(pi * v),
        u,
        v
    };
}

}

// Create array of points with quads that make a unit sphere.
std::vector<Point> sphere(int hSize, int vSize) {
    std::vector<Point> pt;
    for (int i = 0; i < hSize; i++) {
        for (int j = 0; j < vSize; j++) {
            float u0 = (float)i / (float)hSize;
            float u1 = (float)(i + 1) / (float)hSize;
            float v0 = (float)j / (float)vSize;
            float v1 = (float)(j + 1) / float(vSize);
            // Create quad as two triangles.
            pt.push_back(sphere_point(u0, v0));
            pt.push_back(sphere_point(u1, v0));
            pt.push_back(sphere_point(u0, v1));
            pt.push_back(sphere_point(u0, v1));
            pt.push_back(sphere_point(u1, v0));
            pt.push_back(sphere_point(u1, v1));
        }
    }
}

请注意,您可以进行一些简单的优化,还请注意,由于舍入误差,接缝可能无法完全正确对齐。这些留给读者作为练习。

更多问题

即使使用固定版本,您也可能会在两极看到伪影。这是因为屏幕空间纹理坐标导数在极点处具有奇异性。

解决此问题的推荐方法是改用立方体贴图纹理。这也将大大简化球体几何数据,因为您可以完全消除 UV 坐标并且不会有接缝。

作为一个组合,您可以启用各向异性过滤。

【讨论】:

  • 谢谢。我会做一些测试并回复你。
  • 我认为我以前不太了解 UV 映射方面,但现在我明白了。一切正常,失真最小 - 谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-05
  • 1970-01-01
  • 2011-12-09
相关资源
最近更新 更多