看一下中间绿色矩形的左下角:
他们在左下角缺少一个像素。
我画的是这样的:
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{};
};
Run Code Online (Sandbox Code Playgroud)
该代码使用了我的一堆辅助方法/类,但您应该能够知道它的作用。我尝试使用简单的 GL_LINE_LOOP 绘制矩形,但有同样的问题,所以现在我尝试 GL_LINES 并以相同的方向绘制所有线条:从上到下,从左到右,但即使如此,我仍然缺少一个像素。
这些坐标经过正交投影:
gl_Position = projection * model * vec4(inPos, 0.0, 1.0);
Run Code Online (Sandbox Code Playgroud)
因此着色器将这些 0.5 坐标缩放为像素坐标,但我不认为这是舍入错误。
我还可以尝试其他什么方法来对齐该角吗?
OpenGL 对于光栅化线条的实现方式提供了很大的余地。它需要一些理想的属性,但这些属性并不能防止混合 x 大调(“水平”)和 y 大调(“垂直”)线时出现间隙。
首先,“规范的精神”是栅格化半开线;即包含第一个顶点并排除最后一个顶点。因此,您应该确保每个顶点作为源出现一次,作为目标出现一次:
_idxBuf.bufferData<GLuint>({0,1,1,2,2,3,3,0},BufferUsage::StaticDraw);
Run Code Online (Sandbox Code Playgroud)
这与您“从上到下、从左到右”绘制的尝试相反。
GL_LINE_LOOP虽然已经这样做了,但你说这并不能解决问题。这确实不能保证解决问题,因为您在这里混合了 x 大调和 y 大调线,但您仍然应该遵循规则才能使下一点起作用。
接下来,我敢打赌,你的一些顶点恰好落在像素之间;即窗口坐标小数部分恰好为零。当光栅化这些图元时,不同实现之间的差异(或者在我们的例子中,同一实现上的 x 大调线和 y 大调线之间)的差异变得突出。
要解决这个问题,您可以将顶点捕捉到像素网格:
// xy - vertex in window coordinates, i.e. same as gl_FragCoord
xy = floor(xy) + 0.5
Run Code Online (Sandbox Code Playgroud)
您可以在 C++ 中或在顶点着色器中执行此操作。无论哪种情况,您都需要应用投影和视口变换,然后撤消它们,以便 OpenGL 之后可以重新应用它们。这很丑,我知道。
然而,光栅化像素完美线条的唯一万无一失的方法是渲染三角形以覆盖形状(单独的每条线或整个矩形)并从gl_FragCoord.xy片段着色器中分析计算覆盖范围。