在球体上贴纹理时出现了黑色带。

3
我正在使用这段代码来生成球体的顶点和纹理,但是从图片中可以看到,当我旋转它时,可以看到一个黑色带状区域。
 for (int i = 0; i <= stacks; ++i)
    {
        float s = (float)i / (float) stacks;
        float theta = s * 2 * glm::pi<float>();

        for (int j = 0; j <= slices; ++j)
        {
            float sl = (float)j / (float) slices;
            float phi = sl * (glm::pi<float>());

            const float x = cos(theta) * sin(phi);
            const float y = sin(theta) * sin(phi);
            const float z = cos(phi);

            sphere_vertices.push_back(radius * glm::vec3(x, y, z));
            sphere_texcoords.push_back((glm::vec2((x + 1.0) / 2.0, (y + 1.0) / 2.0)));
        }
    }

    // get the indices

    for (int i = 0; i < stacks * slices + slices; ++i)
    {
        sphere_indices.push_back(i);
        sphere_indices.push_back(i + slices + 1);
        sphere_indices.push_back(i + slices);

        sphere_indices.push_back(i + slices + 1);
        sphere_indices.push_back(i);
        sphere_indices.push_back(i + 1);
    }

无论我使用什么纹理坐标,我都无法找到使它正确的方法。

ball

嗯...如果我使用另一张图片,那么映射就会不同(而且更糟糕!)

ball2

顶点着色器:

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aTexCoord;

out vec4 vertexColor;
out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0);
    vertexColor = vec4(0.5, 0.2, 0.5, 1.0);
    TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

片元着色器:

#version 330 core
out vec4 FragColor;

in vec4 vertexColor;
in vec2 TexCoord;

uniform sampler2D sphere_texture;

void main()
{

    FragColor = texture(sphere_texture, TexCoord);
}

我没有使用任何光线条件。


如果我在片元着色器中使用 FragColor = vec4(TexCoord.x, TexCoord.y, 0.0f, 1.0f); (用于调试目的),我会得到一个漂亮的球体。

ball3

我正在使用这个作为纹理:

texture


片段着色器中哪个代码路径输出了黑色像素?这是一个光照计算吗?我看到暗带垂直于光照热点,而不是几何赤道上的位置。 - Wyck
我想看到colors-from-texcoord和黑带在同一摄像头和方向下并排显示,以及纹理本身。无法确定黑色像素是否在您的纹理中。换句话说,当您切换到纹理时,最后一个colors-from-textcoords图像的哪一部分会变黑? - Wyck
@Spektre:对于重复的部分,您是指像这样的内容吗:sphere_vertices.push_back(radius * glm::vec3(0, 0, 1));?(因为当i = 0和j = 0时,x、y、z分别为0、0、1)。并且将循环改为for (int i = 0; i < stacks; ++i),for (int j = 0; j < slices; ++j),而不是<=stacks?我尝试了一下,但仍然出现了黑色条纹,尽管它在图像纹理中有一个良好的间隙。 - George
很难说,根据您的评论,您没有使用类似于这样的矩形纹理映射应用地球纹理映射到球体,而是使用了一些未描述的非线性映射,从代码中分析太麻烦了。然而,从纹理上看,您可能也遇到了光照问题(图像的亮度不均匀),但最有可能的是您在映射边缘处遇到了一些精度限制(您知道斜率随着您的映射方式趋近于无穷大)。 - Spektre
你的风格更像是最后一个等距例子,但你的纹理没有相同的属性...(反射背面而不是?)... - Spektre
显示剩余9条评论
2个回答

4
那张你提供的网球图片揭示了问题,我很高兴你最终提供了它。

enter image description here

你的图片是一个四通道PNG格式的带透明度(Alpha通道)的图像。黄色部分外围有透明像素,其(R,G,B,A)值为(0, 0, 0, 0),所以如果忽略A通道,则(R, G, B)值为(0, 0, 0)即黑色。
这里只展示了红、绿、蓝(RGB)三个通道:

enter image description here

这里只是 Alpha(A)通道。

enter image description here

重要的是要注意球的圆圈没有填满正方形。从球的范围到纹理边缘有53像素的黑边。我们可以从中计算出球的半径。一半的宽度是1000个像素,其中53个像素未使用。球的半径为1000-53,即947像素。大约是距离纹理中心到边缘的距离的94.7%。剩余的5.3%距离是黑色的。
引用:
旁注:我还注意到您的球没有完全达到100%的不透明度。球的黄色部分的alpha通道值为254(255),意味着99.6%的不透明度。白线和闪亮的热点确实达到了100%的不透明度,使其具有类似于死星的外观。;)
要解决您的问题,有直观的方法(可能不起作用),然后有两件事情需要做,这将起作用。以下是您可以执行的几件事:
直观解决方案:这不会完全让您到达100%。

1) 将球的大小调整为填充纹理。使用图像编辑软件将球放大以填充纹理,或修剪掉黑色像素。这将使像素更有效地利用,而且会确保边界处有有用的像素被采样。您可能需要将图像扩展到略大于100%。我将在下面解释原因。 2) 重新映射您的纹理坐标,仅延伸到球半径的94.7%。(类似于方法1,但不需要图像编辑)。这只是使用实际对应于提供的图像的坐标。您的xy坐标需要围绕图像中心缩放并缩小到约94.7%。

x2 = 0.5 + (x - 0.5) * 0.947;
y2 = 0.5 + (y - 0.5) * 0.947;

建议的解决方案:

这将确保不再出现黑色。

3)用一个不那么令人反感的颜色填充你的球纹理中的"黑色"部分 - 可能是网球的周长颜色。这样可以确保任何在球的边缘被精确采样的纹理单元都不会与黑色线性组合,产生一条不美观的深但并非完全黑色的带子,这几乎是你现在所面临的问题。你可以用两种方法来做到这一点。A) 图像编辑软件。去掉图像的透明度,并将其嵌套在深黄色背景下。B) 使用着色器检测图像外的像素,并用边框颜色替换它们(这很聪明,但可能比值得的麻烦要多一些)。

不同的纹理坐标

你可以避免这个退化贴图坐标问题。在赤道上,你无法确定采样哪些像素。球的黑色(透明)像素或有颜色的像素。方形像素的离散性与纹理映射的极性相互抵触。你永远无法在边缘附近找到所需的确切颜色,以产生连续无缝的贴图。相反,你可以使用不同的坐标系统。我希望你不会太在意这个球看起来的样子,因为让我向你介绍等经纬度投影。它是同样的投影方式,你可以朴素地用它将地球球面映射到一个你可能熟悉的典型的世界矩形地图中,在那里南北极扭曲,而赤道地区看起来很好。
下面是你的图像映射到等经纬度坐标系的结果:

enter image description here

请注意底部的黑色条纹...我们找到了一些线索!那个黑色条纹实际上就是您当前贴图坐标系统中出现在球体赤道周围的内容。但是使用这个坐标系统,您可以轻松地看到如果我们将球重新映射以填满正方形,就完全可以消除任何透明像素。
在这个坐标系统中工作可能有点不方便,但您可以使用Photoshop中的“滤镜”>“扭曲”>“极坐标...”>“极坐标转换为矩形”来转换您的图像。 Sigismondo的回答已经建议如何调整您的贴图坐标以执行此操作。
最后,这是一个同时放大以填充纹理空间并重新映射为等距坐标的纹理。没有黑色条纹,失真很小。但是您必须使用Sigismondo的贴图坐标。再次强调,如果您想使用直接投影贴图(即:如果您不想操作网球图像并希望使用该投影),则可能不适合您。但是,如果您愿意重新映射数据,则可以放心,所有黑色像素都将消失!

enter image description here

祝你好运!如有需要,请随时询问。


好的!现在我明白了!非常感谢你在这方面的努力!我完全没有考虑到图像属性,只是想着代码部分...(点赞) - George
很棒的分析!我没想到你能够将圆映射到球体上(原始代码)- 我假设原始图像是“等距矩形”的,没有看到它 - 以为这是唯一的方法,你可以在球体上映射纹理。一个问题:使用原始映射,纹理将被映射“两次”(z正和z负具有相同的x,y坐标)。仅凭这一点就让我认为它是错误的。这种方法是否合理并且实际应用中使用 - 我的意思是除了你在这个特定情况下使用的非线性映射之外? - Sigi
1
@Sigismondo,你说的没错,它将被映射两次。如果纹理位于XY平面上,则具有相同XY坐标的球体上的每个点都将获得相同的纹理坐标 - 对于以原点为中心的球体,始终存在两个具有相同XY的点(除了Z = 0平面上的点:赤道周围的圆)。这被称为正交平面投影。我建议参考这组优秀的图示。【https://learn.foundry.com/modo/content/help/pages/shading_lighting/shader_items/projection_type_samples.html】 - Wyck

0

我无法测试它,因为代码不完整,但是从粗略的查看中,我发现了这个问题:

sphere_texcoords.push_back((glm::vec2((x + 1.0) / 2.0, (y + 1.0) / 2.0)));

纹理坐标不应该从x和y中计算,应为:

const float x = cos(theta) * sin(phi);
const float y = sin(theta) * sin(phi);

但从角度thta-phi或堆栈切片来看,这可能效果更好-未经测试:

sphere_texcoords.push_back(glm::vec2(s,sl));

已被定义:

float s = (float)i / (float) stacks;
float sl = (float)j / (float) slices;

此外,在您的代码中,您正在使用球体的第一个和最后一个“切片”作为其余部分...它们不应该被区别对待吗?这对我来说似乎相当奇怪 - 但我不知道您的实现是否只是更简单的一种,能够正常工作。
例如,请参考以下解释:http://www.songho.ca/opengl/gl_sphere.html

我使用了 vec2(s, sl),但是我收到了这张图片。对于你给我的链接中的代码,我已经使用过了,结果显示出相同的黑色条纹。 - George
2
我找到了这个有趣的链接:http://paulbourke.net/geometry/spherical/ - Sigi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接