使用 OpenGL 使用水平和垂直线渲染纹理的工件
Artifacts with rendering texture with horizontal and vertical lines with OpenGL
我创建了 8x8 像素位图字母以使用 OpenGL 渲染它们,但有时,根据缩放比例,我会得到奇怪的伪像,如下图所示。纹理过滤设置为最近的像素。看起来像是四舍五入的问题,但是如果这条线是完全水平的,怎么会有一些。
左侧原始 8x8,中间缩放为 18x18,右侧缩放为 54x54。
顶点数据是格式为(x 偏移量、y 偏移量、字母)的无符号字节。这是完整的代码:
顶点着色器:
#version 330 core
layout(location = 0) in uvec3 Data;
uniform float ratio;
uniform float font_size;
out float letter;
void main()
{
letter = Data.z;
vec2 position = vec2(float(Data.x) / ratio, Data.y) * font_size - 1.0f;
position.y = -position.y;
gl_Position = vec4(position, 0.0f, 1.0f);
}
几何着色器:
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
uniform float ratio;
uniform float font_size;
out vec3 texture_coord;
in float letter[];
void main()
{
// TODO: pre-calculate
float width = font_size / ratio;
float height = -font_size;
texture_coord = vec3(0.0f, 0.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(0.0f, height, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(1.0f, 0.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(width, height, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(0.0f, 1.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(0.0f, 0.0f, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(1.0f, 1.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(width, 0.0f, 0.0f, 0.0f);
EmitVertex();
EndPrimitive();
}
片段着色器:
#version 330 core
in vec3 texture_coord;
uniform sampler2DArray font_texture_array;
out vec4 output_color;
void main()
{
output_color = texture(font_texture_array, texture_coord);
}
好吧,当使用最近过滤时,如果您的样本位置非常靠近两个纹素之间的边界,您就会看到此类问题。由于要为您绘制的每个片段分别插值 tex 坐标,因此轻微的数值不准确将导致这两个 texel 之间的跳跃。
当您将 8x8 纹理绘制到 18x18 像素大矩形,并且您的矩形与 putput 像素光栅完美对齐时,几乎可以保证触发该行为:
查看纹素坐标会发现,对于最底部的输出像素,纹理坐标将被内插为 1/(2*18) = 1/36。向上移动一个像素将使 t 坐标增加 1/18 = 2/36。所以对于从底部开始的第五行,它将是 9/36。
因此,对于您从中采样的 8x8 纹素大纹理,您实际上是在非标准化纹素坐标 (9/36)*8 == 2.0 处采样。这正是纹理的第二行和第三行之间的边界。由于每个片段的纹理坐标是通过分配给三角形三个顶点的纹理坐标之间的重心插值进行插值的,因此可能存在轻微的不准确。在这种情况下,即使以浮点格式表示的最轻微的不准确性也会导致两个纹素之间的翻转。
我觉得你的方法不太好。缩放位图字体总是有问题的(也许除了整数比例因子之外)。如果你想要好看的可缩放纹理字体,我建议你看看 signed distance fields. It is quite a simple and powerful technique, and there are tools available to generate the necessary distance field textures.
如果您正在寻找快速破解方法,您也可以稍微偏移输出矩形。您基本上必须确保将偏移量保持在 [-0.5,0.5] 像素(以便在光栅化期间不会生成不同的片段,并且您必须确保所有潜在的样本位置永远不会接近整数,因此偏移量将取决于实际比例因子。
我在使用 Freetype 和 OpenGL 开发时遇到了同样的问题。经过几天的研究和摸索,我找到了解决方案。在我的例子中,我必须显式调用函数 'glBlendColor'。有一次,我这样做了,我没有再观察到任何伪影。
这是一个片段:
//Set Viewport
glViewport(0, 0, FIXED_WIDTH, FIXED_HEIGHT);
//Enable Blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendColor(1.0f, 1.0f, 1.0f, 1.0f); //Without this I was having artifacts: IMPORTANT TO EXPLICITLY CALLED
//Set Alignment requirement to 1 byte
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
我在 github 上查看了这个 OpenGL-Freetype 库的源代码后找到了解决方案:opengl-freetype library
我创建了 8x8 像素位图字母以使用 OpenGL 渲染它们,但有时,根据缩放比例,我会得到奇怪的伪像,如下图所示。纹理过滤设置为最近的像素。看起来像是四舍五入的问题,但是如果这条线是完全水平的,怎么会有一些。
左侧原始 8x8,中间缩放为 18x18,右侧缩放为 54x54。
顶点数据是格式为(x 偏移量、y 偏移量、字母)的无符号字节。这是完整的代码:
顶点着色器:
#version 330 core
layout(location = 0) in uvec3 Data;
uniform float ratio;
uniform float font_size;
out float letter;
void main()
{
letter = Data.z;
vec2 position = vec2(float(Data.x) / ratio, Data.y) * font_size - 1.0f;
position.y = -position.y;
gl_Position = vec4(position, 0.0f, 1.0f);
}
几何着色器:
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
uniform float ratio;
uniform float font_size;
out vec3 texture_coord;
in float letter[];
void main()
{
// TODO: pre-calculate
float width = font_size / ratio;
float height = -font_size;
texture_coord = vec3(0.0f, 0.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(0.0f, height, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(1.0f, 0.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(width, height, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(0.0f, 1.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(0.0f, 0.0f, 0.0f, 0.0f);
EmitVertex();
texture_coord = vec3(1.0f, 1.0f, letter[0]);
gl_Position = gl_in[0].gl_Position + vec4(width, 0.0f, 0.0f, 0.0f);
EmitVertex();
EndPrimitive();
}
片段着色器:
#version 330 core
in vec3 texture_coord;
uniform sampler2DArray font_texture_array;
out vec4 output_color;
void main()
{
output_color = texture(font_texture_array, texture_coord);
}
好吧,当使用最近过滤时,如果您的样本位置非常靠近两个纹素之间的边界,您就会看到此类问题。由于要为您绘制的每个片段分别插值 tex 坐标,因此轻微的数值不准确将导致这两个 texel 之间的跳跃。
当您将 8x8 纹理绘制到 18x18 像素大矩形,并且您的矩形与 putput 像素光栅完美对齐时,几乎可以保证触发该行为:
查看纹素坐标会发现,对于最底部的输出像素,纹理坐标将被内插为 1/(2*18) = 1/36。向上移动一个像素将使 t 坐标增加 1/18 = 2/36。所以对于从底部开始的第五行,它将是 9/36。
因此,对于您从中采样的 8x8 纹素大纹理,您实际上是在非标准化纹素坐标 (9/36)*8 == 2.0 处采样。这正是纹理的第二行和第三行之间的边界。由于每个片段的纹理坐标是通过分配给三角形三个顶点的纹理坐标之间的重心插值进行插值的,因此可能存在轻微的不准确。在这种情况下,即使以浮点格式表示的最轻微的不准确性也会导致两个纹素之间的翻转。
我觉得你的方法不太好。缩放位图字体总是有问题的(也许除了整数比例因子之外)。如果你想要好看的可缩放纹理字体,我建议你看看 signed distance fields. It is quite a simple and powerful technique, and there are tools available to generate the necessary distance field textures.
如果您正在寻找快速破解方法,您也可以稍微偏移输出矩形。您基本上必须确保将偏移量保持在 [-0.5,0.5] 像素(以便在光栅化期间不会生成不同的片段,并且您必须确保所有潜在的样本位置永远不会接近整数,因此偏移量将取决于实际比例因子。
我在使用 Freetype 和 OpenGL 开发时遇到了同样的问题。经过几天的研究和摸索,我找到了解决方案。在我的例子中,我必须显式调用函数 'glBlendColor'。有一次,我这样做了,我没有再观察到任何伪影。
这是一个片段:
//Set Viewport
glViewport(0, 0, FIXED_WIDTH, FIXED_HEIGHT);
//Enable Blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendColor(1.0f, 1.0f, 1.0f, 1.0f); //Without this I was having artifacts: IMPORTANT TO EXPLICITLY CALLED
//Set Alignment requirement to 1 byte
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
我在 github 上查看了这个 OpenGL-Freetype 库的源代码后找到了解决方案:opengl-freetype library