OpenGL:如何修复矩形中缺失的角像素(线或线环)
OpenGL: How to fix missing corner pixel in rect (lines or line loop)
看看中间绿色矩形的左下角:
它们在左下角缺少一个像素。
我画的是这样的:
class Rect: public StaticModel {
public:
Rect() {
constexpr glm::vec2 vertices[] {
{-0.5,0.5}, // top left
{0.5,0.5}, // top right
{0.5,-0.5}, // bottom right
{-0.5,-0.5}, // bottom left
};
_buf.bufferData<glm::vec2>(vertices,BufferUsage::StaticDraw);
_idxBuf.bufferData<GLuint>({0,1,3,2,0,3,1,2},BufferUsage::StaticDraw);
}
void bind() const override {
_buf.bindVertex();
_idxBuf.bind();
}
void draw() const override {
gl::drawElements(8,DrawMode::Lines);
}
private:
VertexBuffer _buf{sizeof(glm::vec2)};
ElementArrayBuffer _idxBuf{};
};
该代码使用了我的一堆助手 methods/classes,但您应该能够分辨出它的作用。我尝试使用简单的 GL_LINE_LOOP 绘制矩形,但遇到了同样的问题,所以现在我正在尝试 GL_LINES 并沿相同方向绘制所有线条:从上到下,从左到右,但是即便如此,我仍然缺少一个像素。
这些坐标正在进行正交投影:
gl_Position = projection * model * vec4(inPos, 0.0, 1.0);
所以着色器将这些 0.5 坐标缩放到像素坐标,但我认为这不是舍入误差。
还有什么我可以尝试让那个角对齐的吗?
OpenGL 为实现如何光栅化线条提供了很多余地。它需要一些理想的属性,但在混合 x-major ('horizontal') 和 y-major ('vertical') 线时,这些属性不会阻止间隙。
首先,“规范精神”是将半开线栅格化;即包括第一个顶点并排除最后一个顶点。出于这个原因,您应该确保每个顶点仅作为源出现一次,作为目的地出现一次:
_idxBuf.bufferData<GLuint>({0,1,1,2,2,3,3,0},BufferUsage::StaticDraw);
这与您“从上到下,从左到右”的绘制尝试相反。
GL_LINE_LOOP
虽然已经这样做了,但您说这不能解决问题。这确实不能保证解决问题,因为你在这里混合了 x-major 和 y-major 行,但你仍然应该遵循规则才能使下一个点起作用。
接下来,我敢打赌,您的某些顶点正好落在像素之间;即 window 坐标小数部分恰好为零。当光栅化这些基元时,不同实现之间的差异(或者在我们的例子中,同一实现上的 x 主线和 y 主线之间的差异)变得突出。
要解决这个问题,您可以将顶点捕捉到像素网格:
// xy - vertex in window coordinates, i.e. same as gl_FragCoord
xy = floor(xy) + 0.5
您可以在 C++ 或顶点着色器中执行此操作。在任何一种情况下,您都需要应用投影和视口变换,然后撤消它们,以便 OpenGL 之后可以重新应用它们。这很丑,我知道。
然而,光栅化像素完美线的唯一可靠方法是渲染三角形以覆盖形状(单独的每条线或整个矩形)并从 gl_FragCoord.xy
分析计算覆盖率片段着色器。
看看中间绿色矩形的左下角:
它们在左下角缺少一个像素。
我画的是这样的:
class Rect: public StaticModel {
public:
Rect() {
constexpr glm::vec2 vertices[] {
{-0.5,0.5}, // top left
{0.5,0.5}, // top right
{0.5,-0.5}, // bottom right
{-0.5,-0.5}, // bottom left
};
_buf.bufferData<glm::vec2>(vertices,BufferUsage::StaticDraw);
_idxBuf.bufferData<GLuint>({0,1,3,2,0,3,1,2},BufferUsage::StaticDraw);
}
void bind() const override {
_buf.bindVertex();
_idxBuf.bind();
}
void draw() const override {
gl::drawElements(8,DrawMode::Lines);
}
private:
VertexBuffer _buf{sizeof(glm::vec2)};
ElementArrayBuffer _idxBuf{};
};
该代码使用了我的一堆助手 methods/classes,但您应该能够分辨出它的作用。我尝试使用简单的 GL_LINE_LOOP 绘制矩形,但遇到了同样的问题,所以现在我正在尝试 GL_LINES 并沿相同方向绘制所有线条:从上到下,从左到右,但是即便如此,我仍然缺少一个像素。
这些坐标正在进行正交投影:
gl_Position = projection * model * vec4(inPos, 0.0, 1.0);
所以着色器将这些 0.5 坐标缩放到像素坐标,但我认为这不是舍入误差。
还有什么我可以尝试让那个角对齐的吗?
OpenGL 为实现如何光栅化线条提供了很多余地。它需要一些理想的属性,但在混合 x-major ('horizontal') 和 y-major ('vertical') 线时,这些属性不会阻止间隙。
首先,“规范精神”是将半开线栅格化;即包括第一个顶点并排除最后一个顶点。出于这个原因,您应该确保每个顶点仅作为源出现一次,作为目的地出现一次:
_idxBuf.bufferData<GLuint>({0,1,1,2,2,3,3,0},BufferUsage::StaticDraw);
这与您“从上到下,从左到右”的绘制尝试相反。
GL_LINE_LOOP
虽然已经这样做了,但您说这不能解决问题。这确实不能保证解决问题,因为你在这里混合了 x-major 和 y-major 行,但你仍然应该遵循规则才能使下一个点起作用。接下来,我敢打赌,您的某些顶点正好落在像素之间;即 window 坐标小数部分恰好为零。当光栅化这些基元时,不同实现之间的差异(或者在我们的例子中,同一实现上的 x 主线和 y 主线之间的差异)变得突出。
要解决这个问题,您可以将顶点捕捉到像素网格:
// xy - vertex in window coordinates, i.e. same as gl_FragCoord xy = floor(xy) + 0.5
您可以在 C++ 或顶点着色器中执行此操作。在任何一种情况下,您都需要应用投影和视口变换,然后撤消它们,以便 OpenGL 之后可以重新应用它们。这很丑,我知道。
然而,光栅化像素完美线的唯一可靠方法是渲染三角形以覆盖形状(单独的每条线或整个矩形)并从 gl_FragCoord.xy
分析计算覆盖率片段着色器。