纹理球体上的球有一条暗带
texture a ball on a sphere has a dark band
我正在使用此代码生成球体顶点和纹理,但正如您在图像中看到的那样,当我旋转它时,我可以看到一个暗带。
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);
}
无论我使用什么纹理坐标,我都想不出使它正确的方法。
嗯..如果我使用另一个图像,那么映射就会不同(而且最差!)
顶点着色器:
#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);
(用于调试目的),我会收到一个漂亮的球体。
我将其用作纹理:
由于代码不完整,我无法测试,但粗略地看了一下,我发现了这个问题:
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,或者stacks-slices。这可能会更好 - 未经测试:
sphere_texcoords.push_back(glm::vec2(s,sl));
已经定义:
float s = (float)i / (float) stacks;
float sl = (float)j / (float) slices;
此外,在您的代码中,您使用球体的第一个和最后一个 "slices" 作为其余部分……难道不应该区别对待它们吗?这对我来说似乎很奇怪 - 但我不知道您的实现是否只是一个更简单的实现,工作正常。
对比一下这个解释,例如:http://www.songho.ca/opengl/gl_sphere.html
您链接的网球图片揭示了问题所在。很高兴你最终提供了它。
您的图像是具有透明度的四通道 PNG(Alpha 通道)。球的黄色部分外侧周围都有透明像素,它们具有 (R,G,B,A) = (0, 0, 0, 0),因此如果您忽略 A 通道,则 (R, G, B), 将是 (0, 0, 0) = 黑色。
这里只是红色、绿色和蓝色 (RGB) 通道:
这里只是 Alpha (A) 通道。
需要注意的重要一点是球的圆圈没有填满正方形。从球的范围到纹理的边缘有 53 个黑色像素的显着边缘。我们可以由此计算出球的半径。一半宽度为1000像素,其中53像素未使用。球的半径为 1000-53,即 947 像素。或大约 94.7% 从中心到纹理边缘的距离。剩下的5.3%的距离是黑色的。
Side note: I also notice that your ball doesn't quite reach 100% opacity. The yellow part of the ball has an alpha channel value of 254 (of 255) Meaning 99.6% opaque. The white lines and the shiny hot spot do actually reach 100% opacity, giving it sort of a Death Star look. ;)
要解决您的问题,可以使用直观的方法(可能行不通),然后您需要做两件事才能奏效。以下是您可以做的几件事:
直观的解决方案:
这不会让你 100% 达到目标。
1) 调整球的大小以填充纹理。使用图像编辑软件放大球以填充纹理,或将黑色像素trim 掉。一方面,这只会更有效地利用像素,但它会确保在边界处采样有用的像素。您可能希望将图像放大到略大于 100%。我将在下面解释原因。
2) 重新映射您的纹理坐标,使其仅扩展到球半径的 94.7%。 (类似于方法 1,但不需要图像编辑)。这仅使用与您提供的图像实际对应的坐标。您的 x
和 y
坐标需要围绕图像中心进行缩放并缩小到大约 94.7%。
x2 = 0.5 + (x - 0.5) * 0.947;
y2 = 0.5 + (y - 0.5) * 0.947;
建议的解决方案:
这样就可以保证不再黑
3) 用不太令人反感的颜色填充球纹理的 "black" 部分 - 可能是网球圆周处的颜色。这确保了在球的边缘精确采样的任何纹素不会与黑色线性组合以产生难看的黑暗但不完全是黑色的带,这几乎是你现在遇到的问题。您可以通过两种方式执行此操作。 A) 图像编辑软件。从图像中去除透明度并将其遮罩在深黄色上。 B) 使用着色器检测图像外部的像素并用边框颜色替换它们(这很聪明,但可能比它的价值更麻烦。)
不同的纹理坐标
你能做的最后一件事就是完全避免这种退化的纹理贴图坐标问题。在赤道,您不确定要对哪些像素进行采样。球的黑色(透明)像素或彩色像素。正方形像素的离散性质与纹理贴图的极性性质作斗争。您永远无法在边缘附近找到生成连续、无缝地图所需的确切颜色。相反,您可以使用不同的坐标系。我希望您不在意那个球的外观,因为让我向您介绍 等距柱状投影 。您可以天真地使用相同的投影将地球映射到您可能熟悉的典型矩形世界地图,其中北极和南极会出现所有变形,但赤道地区看起来相当不错。
这是映射到等距柱状坐标的图像:
注意底部的黑条...我们找到了一些东西!那个黑条实际上就是在你当前的纹理映射坐标系下你的球的赤道周围出现的。但是使用这个坐标系,您可以很容易地看到,如果我们只是重新映射球以填充正方形,我们将完全消除任何透明像素。
在此坐标系中工作可能不方便,但您可以在 Photoshop 中使用“滤镜”>“扭曲”>“极坐标...”>“极坐标到矩形”来转换图像。
已经建议如何调整纹理贴图坐标。
最后,这是一个纹理,它被放大以填充纹理 space,并重新映射到等距柱状坐标。没有黑条,失真最小。但是您必须使用 Sigismondo 的纹理贴图坐标。同样,这可能不适合您,特别是如果您执着于纹理直接投影的想法(即:如果您不想操纵网球图像并且想使用该投影。)但是如果你愿意重新映射你的数据,你可以高枕无忧,所有的黑色像素都会消失!
祝你好运!随时要求澄清。
我正在使用此代码生成球体顶点和纹理,但正如您在图像中看到的那样,当我旋转它时,我可以看到一个暗带。
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);
}
无论我使用什么纹理坐标,我都想不出使它正确的方法。
嗯..如果我使用另一个图像,那么映射就会不同(而且最差!)
顶点着色器:
#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);
(用于调试目的),我会收到一个漂亮的球体。
我将其用作纹理:
由于代码不完整,我无法测试,但粗略地看了一下,我发现了这个问题:
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,或者stacks-slices。这可能会更好 - 未经测试:
sphere_texcoords.push_back(glm::vec2(s,sl));
已经定义:
float s = (float)i / (float) stacks;
float sl = (float)j / (float) slices;
此外,在您的代码中,您使用球体的第一个和最后一个 "slices" 作为其余部分……难道不应该区别对待它们吗?这对我来说似乎很奇怪 - 但我不知道您的实现是否只是一个更简单的实现,工作正常。
对比一下这个解释,例如:http://www.songho.ca/opengl/gl_sphere.html
您链接的网球图片揭示了问题所在。很高兴你最终提供了它。
您的图像是具有透明度的四通道 PNG(Alpha 通道)。球的黄色部分外侧周围都有透明像素,它们具有 (R,G,B,A) = (0, 0, 0, 0),因此如果您忽略 A 通道,则 (R, G, B), 将是 (0, 0, 0) = 黑色。
这里只是红色、绿色和蓝色 (RGB) 通道:
这里只是 Alpha (A) 通道。
需要注意的重要一点是球的圆圈没有填满正方形。从球的范围到纹理的边缘有 53 个黑色像素的显着边缘。我们可以由此计算出球的半径。一半宽度为1000像素,其中53像素未使用。球的半径为 1000-53,即 947 像素。或大约 94.7% 从中心到纹理边缘的距离。剩下的5.3%的距离是黑色的。
Side note: I also notice that your ball doesn't quite reach 100% opacity. The yellow part of the ball has an alpha channel value of 254 (of 255) Meaning 99.6% opaque. The white lines and the shiny hot spot do actually reach 100% opacity, giving it sort of a Death Star look. ;)
要解决您的问题,可以使用直观的方法(可能行不通),然后您需要做两件事才能奏效。以下是您可以做的几件事:
直观的解决方案:
这不会让你 100% 达到目标。
1) 调整球的大小以填充纹理。使用图像编辑软件放大球以填充纹理,或将黑色像素trim 掉。一方面,这只会更有效地利用像素,但它会确保在边界处采样有用的像素。您可能希望将图像放大到略大于 100%。我将在下面解释原因。
2) 重新映射您的纹理坐标,使其仅扩展到球半径的 94.7%。 (类似于方法 1,但不需要图像编辑)。这仅使用与您提供的图像实际对应的坐标。您的 x
和 y
坐标需要围绕图像中心进行缩放并缩小到大约 94.7%。
x2 = 0.5 + (x - 0.5) * 0.947;
y2 = 0.5 + (y - 0.5) * 0.947;
建议的解决方案:
这样就可以保证不再黑
3) 用不太令人反感的颜色填充球纹理的 "black" 部分 - 可能是网球圆周处的颜色。这确保了在球的边缘精确采样的任何纹素不会与黑色线性组合以产生难看的黑暗但不完全是黑色的带,这几乎是你现在遇到的问题。您可以通过两种方式执行此操作。 A) 图像编辑软件。从图像中去除透明度并将其遮罩在深黄色上。 B) 使用着色器检测图像外部的像素并用边框颜色替换它们(这很聪明,但可能比它的价值更麻烦。)
不同的纹理坐标
你能做的最后一件事就是完全避免这种退化的纹理贴图坐标问题。在赤道,您不确定要对哪些像素进行采样。球的黑色(透明)像素或彩色像素。正方形像素的离散性质与纹理贴图的极性性质作斗争。您永远无法在边缘附近找到生成连续、无缝地图所需的确切颜色。相反,您可以使用不同的坐标系。我希望您不在意那个球的外观,因为让我向您介绍 等距柱状投影 。您可以天真地使用相同的投影将地球映射到您可能熟悉的典型矩形世界地图,其中北极和南极会出现所有变形,但赤道地区看起来相当不错。
这是映射到等距柱状坐标的图像:
注意底部的黑条...我们找到了一些东西!那个黑条实际上就是在你当前的纹理映射坐标系下你的球的赤道周围出现的。但是使用这个坐标系,您可以很容易地看到,如果我们只是重新映射球以填充正方形,我们将完全消除任何透明像素。
在此坐标系中工作可能不方便,但您可以在 Photoshop 中使用“滤镜”>“扭曲”>“极坐标...”>“极坐标到矩形”来转换图像。
最后,这是一个纹理,它被放大以填充纹理 space,并重新映射到等距柱状坐标。没有黑条,失真最小。但是您必须使用 Sigismondo 的纹理贴图坐标。同样,这可能不适合您,特别是如果您执着于纹理直接投影的想法(即:如果您不想操纵网球图像并且想使用该投影。)但是如果你愿意重新映射你的数据,你可以高枕无忧,所有的黑色像素都会消失!
祝你好运!随时要求澄清。