OpenGL Sphere顶点和UV坐标

时间:2017-01-09 20:59:09

标签: c++ opengl

我知道此问题存在许多类似的问题,例如this one,但我似乎无法弄清楚我的程序出了什么问题。

我正在尝试使用朴素经度/纬度方法创建单位球体,然后尝试使用UV坐标围绕球体包裹纹理。

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

北极...... North Pole

南极...... enter image description here

...煤层 enter image description here

图像来自具有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指定两个CCW三角形的顶点...

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

谢谢!

1 个答案:

答案 0 :(得分:2)

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

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只是cal atan2只是额外的工作,在最好的情况下,你可能会得到错误的分支削减。您可以直接从p.uslice计算slice

The Seam

你的球体会有缝隙。这是正常的,大多数模型在某些地方的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 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坐标,并且您将没有接缝。

作为kludge,您可以改为启用各向异性过滤。