c++ - OpenGL 球体顶点和 UV 坐标

标签 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;
}

然后我加载我的数组,为每个四边形指定两个 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);

谢谢!

最佳答案

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

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 坐标并且不会有接缝。

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

关于c++ - OpenGL 球体顶点和 UV 坐标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41556821/

相关文章:

C++ 参数化构造函数使代码在传递大输入时停止工作

c++ - 如何在不使用 C++ OpenGL SuperBible 着色器的情况下使用变换矩阵?

c++ - 使用 OpenGL 围绕枢轴点旋转

c++ - 如何多次锁定互斥锁?

c++ - 在排序和旋转的数组中搜索

c++ - 将已编译的 .res 文件与 CMake 链接

opengl - 使用 GLSL 对顶点数组进行广告牌?

c++ - 我的glsl着色器加载代码有什么问题吗?

c++ - SDL2 : Fast Pixel Manipulation

c++ - 将 QPainter 与 QCoreApplication 一起使用