为什么这个 OpenGL + WIN32 代码会产生竖线?
Why does this OpenGL + WIN32 code produce Vertical lines?
明确地说,我已经广泛测试了这段代码,发现问题出在 WGL 代码之前编写的代码中。它恰好在WIN32代码中。现在我认为这可能部分是由调用 gluOrth2D 引起的,但即便如此,据我所知,这不应该是主要原因。我可能只是通过弄乱东西来自己解决这个问题,但考虑到这个问题(发生在一个更大的项目中)已经占用了我很多时间,我认为值得发布这个以防其他人遇到同样的问题。我希望得到解释以及 fix/correction。
#include <GL/glew.h>
#include <glfw3.h>
#define GLFW_EXPOSE_NATIVE_WGL
#define GLFW_EXPOSE_NATIVE_WIN32
#include <glfw3native.h>
constexpr int width = 1000, height = 1000;
bool running = true;
LRESULT CALLBACK WIN32_processMessage(HWND hwnd, UINT msg,
WPARAM wparam, LPARAM lparam)
{
return DefWindowProcA(hwnd, msg, wparam, lparam);
}
int main()
{
HINSTANCE hinst = GetModuleHandleA(NULL);
HICON icon = LoadIcon(hinst, IDI_APPLICATION);
WNDCLASSA* wc = (WNDCLASSA*)memset(malloc(sizeof(WNDCLASSA)), 0, sizeof(WNDCLASSA));
if (wc == NULL)
{
return -1;
}
wc->style = CS_DBLCLKS;
wc->lpfnWndProc = WIN32_processMessage;
wc->cbClsExtra = 0;
wc->cbWndExtra = 0;
wc->hInstance = hinst;
wc->hIcon = icon;
wc->hCursor = LoadCursor(NULL, IDC_ARROW);
wc->hbrBackground = NULL;
wc->lpszClassName = "toast_window_class";
if (!RegisterClassA(wc))
{
return 1;
}
UINT32 windowX = 100;
UINT32 windowY = 100;
UINT32 windowWidth = width;
UINT32 windowHeight = height;
UINT32 windowStyle = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION;
UINT32 windowExStyle = WS_EX_APPWINDOW;
windowStyle |= WS_MAXIMIZEBOX;
windowStyle |= WS_MINIMIZEBOX;
windowStyle |= WS_THICKFRAME;
RECT borderRect = { 0,0,0,0 };
AdjustWindowRectEx(&borderRect, windowStyle, 0, windowExStyle);
windowX += borderRect.left;
windowY += borderRect.top;
windowWidth += borderRect.right - borderRect.left;
windowWidth += borderRect.bottom - borderRect.top;
HWND window = CreateWindowExA(windowExStyle, "toast_window_class", (LPCSTR)"test",
windowStyle, windowX, windowY, windowWidth, windowHeight, 0, 0, hinst, 0);
if (window == 0)
{
return 2;
}
bool shouldActivate = true;
const int showWindowCommandFlags = shouldActivate ? SW_SHOW : SW_SHOWNOACTIVATE;
ShowWindow(window, showWindowCommandFlags);
// WGL -----------------------------------------------------------
HDC device = GetDC(window);
PIXELFORMATDESCRIPTOR pfd;
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cAlphaBits = 0;
pfd.cAccumBits = 0;
pfd.cDepthBits = 0;
pfd.cStencilBits = 0;
pfd.cAuxBuffers = 0;
pfd.iLayerType = PFD_MAIN_PLANE;
SetPixelFormat(device, ChoosePixelFormat(device, &pfd), &pfd);
HGLRC render = wglCreateContext(device);
wglMakeCurrent(device, render);
// GLEW ---------------------------------------------------------
const GLint res = glewInit();
if (GLEW_OK != res)
{
return 1;
}
// setup OpenGL --------------------------------------------------
gluOrtho2D(0, width, 0, height);
// main loop -----------------------------------------------------
while (running)
{
glClear(GL_COLOR_BUFFER_BIT);
// rendering
glBegin(GL_POINTS);
for (int x = 10; x < width - 10; ++x)
{
for (int y = 10; y < height - 10; ++y)
{
glVertex2f(x, y);
}
}
glEnd();
SwapBuffers(device);
}
return 0;
}
所以我设法修复了它。他们的关键在第 49 和 54 行。我已经注释掉了导致此示例中问题的部分:
UINT32 windowStyle = WS_OVERLAPPED | WS_SYSMENU; //| WS_CAPTION;
UINT32 windowExStyle = WS_EX_APPWINDOW;
windowStyle |= WS_MAXIMIZEBOX;
windowStyle |= WS_MINIMIZEBOX;
//windowStyle |= WS_THICKFRAME;
我不是很熟悉 WIN32 窗口代码。这真的是我第一次尝试。所以我欢迎任何评论,如果有人可以添加一些。
不要使用 GL_POINTS
来绘制图像 pixel-by-pixel。
一方面,由于 OpenGL、其实现和现代 GPU 的工作方式,它可能是(在任何系统、任何配置中)效率最低的方法。相反 - 如果使用 OpenGL - 创建一个所需大小的二维数组,根据需要填充像素值,然后将图片加载到纹理中并绘制一个带纹理的四边形,或者更好的是一个覆盖三角形的矩形,你可以使用 glScissor
.
回到您的 point-to-point 绘图问题。您 运行 的问题本质上是舍入误差和 OpenGL 在模型视图 space 之间转换的方式,通过投影 space 到 NDC 和最后的视口。顺便说一句,您没有调用 glViewport
,因此不会考虑 window 大小的任何调整。
对 glOrtho2D
的调用将设置转换矩阵(在您的情况下是模型视图,因为您没有费心切换到投影矩阵)以便 [0; width]×[0; height]
范围内的坐标将映射到 NDC 坐标 [-1; 1]×[-1; 1]
。这些范围是封闭的,即它们确实包括它们的极限值。然而,像素网格中的像素在整数范围内寻址,范围的末尾为 open(即 ℤ²∩[0; width(×[0; height(
)。这意味着,如果您在不进行调整的情况下将视口范围用于正交投影,像素索引和 OpenGL 坐标之间将存在非常微小的差异。作为练习,我会把它留给你来计算要进行何种调整的数学(提示,你必须将视口范围的某个小数值减去某些 glOrtho 参数)。
现在对于某些 window 大小您不会注意到这个问题,因为对于您放入 OpenGL 中的所有“像素”位置,四舍五入将恰好将它们强制到您想要的像素位置。但是对于其他 window 大小,对于某些坐标值,四舍五入是另一种方式,您将保留 and/or 行未受影响的列。这导致您有时会看到线条。
明确地说,我已经广泛测试了这段代码,发现问题出在 WGL 代码之前编写的代码中。它恰好在WIN32代码中。现在我认为这可能部分是由调用 gluOrth2D 引起的,但即便如此,据我所知,这不应该是主要原因。我可能只是通过弄乱东西来自己解决这个问题,但考虑到这个问题(发生在一个更大的项目中)已经占用了我很多时间,我认为值得发布这个以防其他人遇到同样的问题。我希望得到解释以及 fix/correction。
#include <GL/glew.h>
#include <glfw3.h>
#define GLFW_EXPOSE_NATIVE_WGL
#define GLFW_EXPOSE_NATIVE_WIN32
#include <glfw3native.h>
constexpr int width = 1000, height = 1000;
bool running = true;
LRESULT CALLBACK WIN32_processMessage(HWND hwnd, UINT msg,
WPARAM wparam, LPARAM lparam)
{
return DefWindowProcA(hwnd, msg, wparam, lparam);
}
int main()
{
HINSTANCE hinst = GetModuleHandleA(NULL);
HICON icon = LoadIcon(hinst, IDI_APPLICATION);
WNDCLASSA* wc = (WNDCLASSA*)memset(malloc(sizeof(WNDCLASSA)), 0, sizeof(WNDCLASSA));
if (wc == NULL)
{
return -1;
}
wc->style = CS_DBLCLKS;
wc->lpfnWndProc = WIN32_processMessage;
wc->cbClsExtra = 0;
wc->cbWndExtra = 0;
wc->hInstance = hinst;
wc->hIcon = icon;
wc->hCursor = LoadCursor(NULL, IDC_ARROW);
wc->hbrBackground = NULL;
wc->lpszClassName = "toast_window_class";
if (!RegisterClassA(wc))
{
return 1;
}
UINT32 windowX = 100;
UINT32 windowY = 100;
UINT32 windowWidth = width;
UINT32 windowHeight = height;
UINT32 windowStyle = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION;
UINT32 windowExStyle = WS_EX_APPWINDOW;
windowStyle |= WS_MAXIMIZEBOX;
windowStyle |= WS_MINIMIZEBOX;
windowStyle |= WS_THICKFRAME;
RECT borderRect = { 0,0,0,0 };
AdjustWindowRectEx(&borderRect, windowStyle, 0, windowExStyle);
windowX += borderRect.left;
windowY += borderRect.top;
windowWidth += borderRect.right - borderRect.left;
windowWidth += borderRect.bottom - borderRect.top;
HWND window = CreateWindowExA(windowExStyle, "toast_window_class", (LPCSTR)"test",
windowStyle, windowX, windowY, windowWidth, windowHeight, 0, 0, hinst, 0);
if (window == 0)
{
return 2;
}
bool shouldActivate = true;
const int showWindowCommandFlags = shouldActivate ? SW_SHOW : SW_SHOWNOACTIVATE;
ShowWindow(window, showWindowCommandFlags);
// WGL -----------------------------------------------------------
HDC device = GetDC(window);
PIXELFORMATDESCRIPTOR pfd;
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cAlphaBits = 0;
pfd.cAccumBits = 0;
pfd.cDepthBits = 0;
pfd.cStencilBits = 0;
pfd.cAuxBuffers = 0;
pfd.iLayerType = PFD_MAIN_PLANE;
SetPixelFormat(device, ChoosePixelFormat(device, &pfd), &pfd);
HGLRC render = wglCreateContext(device);
wglMakeCurrent(device, render);
// GLEW ---------------------------------------------------------
const GLint res = glewInit();
if (GLEW_OK != res)
{
return 1;
}
// setup OpenGL --------------------------------------------------
gluOrtho2D(0, width, 0, height);
// main loop -----------------------------------------------------
while (running)
{
glClear(GL_COLOR_BUFFER_BIT);
// rendering
glBegin(GL_POINTS);
for (int x = 10; x < width - 10; ++x)
{
for (int y = 10; y < height - 10; ++y)
{
glVertex2f(x, y);
}
}
glEnd();
SwapBuffers(device);
}
return 0;
}
所以我设法修复了它。他们的关键在第 49 和 54 行。我已经注释掉了导致此示例中问题的部分:
UINT32 windowStyle = WS_OVERLAPPED | WS_SYSMENU; //| WS_CAPTION;
UINT32 windowExStyle = WS_EX_APPWINDOW;
windowStyle |= WS_MAXIMIZEBOX;
windowStyle |= WS_MINIMIZEBOX;
//windowStyle |= WS_THICKFRAME;
我不是很熟悉 WIN32 窗口代码。这真的是我第一次尝试。所以我欢迎任何评论,如果有人可以添加一些。
不要使用 GL_POINTS
来绘制图像 pixel-by-pixel。
一方面,由于 OpenGL、其实现和现代 GPU 的工作方式,它可能是(在任何系统、任何配置中)效率最低的方法。相反 - 如果使用 OpenGL - 创建一个所需大小的二维数组,根据需要填充像素值,然后将图片加载到纹理中并绘制一个带纹理的四边形,或者更好的是一个覆盖三角形的矩形,你可以使用 glScissor
.
回到您的 point-to-point 绘图问题。您 运行 的问题本质上是舍入误差和 OpenGL 在模型视图 space 之间转换的方式,通过投影 space 到 NDC 和最后的视口。顺便说一句,您没有调用 glViewport
,因此不会考虑 window 大小的任何调整。
对 glOrtho2D
的调用将设置转换矩阵(在您的情况下是模型视图,因为您没有费心切换到投影矩阵)以便 [0; width]×[0; height]
范围内的坐标将映射到 NDC 坐标 [-1; 1]×[-1; 1]
。这些范围是封闭的,即它们确实包括它们的极限值。然而,像素网格中的像素在整数范围内寻址,范围的末尾为 open(即 ℤ²∩[0; width(×[0; height(
)。这意味着,如果您在不进行调整的情况下将视口范围用于正交投影,像素索引和 OpenGL 坐标之间将存在非常微小的差异。作为练习,我会把它留给你来计算要进行何种调整的数学(提示,你必须将视口范围的某个小数值减去某些 glOrtho 参数)。
现在对于某些 window 大小您不会注意到这个问题,因为对于您放入 OpenGL 中的所有“像素”位置,四舍五入将恰好将它们强制到您想要的像素位置。但是对于其他 window 大小,对于某些坐标值,四舍五入是另一种方式,您将保留 and/or 行未受影响的列。这导致您有时会看到线条。