OpenGL 缩放单像素线
OpenGL Scale Single Pixel Line
我想制作一个内部分辨率为 320x240 的游戏,但以其整数倍(640x480、960,720 等)呈现到屏幕上。我想要复古的 2D 像素图形。
我通过 glOrtho() 设置内部分辨率实现了这一点:
glOrtho(0, 320, 240, 0, 0, 1);
然后我将输出分辨率放大 3 倍,如下所示:
glViewport(0,0,960,720);
window = SDL_CreateWindow("Title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 960, 720, SDL_WINDOW_OPENGL);
我画的矩形是这样的:
glBegin(GL_LINE_LOOP);
glVertex2f(rect_x, rect_y);
glVertex2f(rect_x + rect_w, rect_y);
glVertex2f(rect_x + dst_w, dst_y + dst_h);
glVertex2f(rect_x, rect_y + rect_h);
glEnd();
它在 320x240(未缩放)下完美运行:
当我放大到 960x720 时,像素渲染一切正常!然而,GL_Line_Loop 似乎不是在 320x240 canvas 上绘制并按比例放大,而是在最终的 960x720 canvas 上绘制。结果是 3px 世界中的 1px 线:(
如何绘制线条到 320x240 glOrtho canvas,而不是 960x720 输出 canvas?
没有“320x240 glOrtho canvas”。只有 window 的实际分辨率:960x720。
您所做的只是放大您渲染的图元的坐标。因此,您的代码表示要呈现从 (20, 20) 到 (40, 40) 的一行。 OpenGL(最终)在每个维度上将这些坐标缩放 3:(60, 60) 和 (120x120)。
但这只是处理端点。中间发生的事情仍然基于您以 window 的实际分辨率渲染的事实。
即使您使用 glLineWidth
来更改线条的宽度,那也只会固定线条宽度。它不会解决线的光栅化基于您渲染的实际分辨率这一事实。所以对角线不会有你可能想要的像素化外观。
正确执行此操作的唯一方法就是正确执行此操作。渲染为实际 320x240 的图像,然后将其绘制为 window 的实际分辨率。
您必须创建该尺寸的纹理,然后将其附加到 framebuffer object。绑定用于渲染的 FBO 并对其进行渲染(将视口设置为图像的大小)。然后取消绑定 FBO,并将该纹理绘制到 window(视口设置为 window 的分辨率)。
正如我在评论中提到的 Intel OpenGL 驱动程序在直接渲染到纹理时存在问题,我不知道有任何可行的解决方法。在这种情况下,唯一的解决方法是使用 glReadPixels
将屏幕内容复制到 CPU 内存中,然后将其复制回 GPU 作为纹理。当然,这比直接渲染到纹理要慢得多。所以这是交易:
设置低分辨率视图
不要只更改 window 的 glViewport
值的分辨率。然后以低分辨率渲染你的场景(只占屏幕的一小部分space)
将渲染的屏幕复制到纹理中
- 设置目标分辨率视图
渲染纹理
不要忘记使用 GL_NEAREST
过滤器。最重要的是你只能在此之后而不是之前交换缓冲区!否则会出现闪烁。
此处为 C++ 源代码:
void gl_draw()
{
// render resolution and multiplier
const int xs=320,ys=200,m=2;
// [low res render pass]
glViewport(0,0,xs,ys);
glClearColor(0.0,0.0,0.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
// 50 random lines
RandSeed=0x12345678;
glColor3f(1.0,1.0,1.0);
glBegin(GL_LINES);
for (int i=0;i<100;i++)
glVertex2f(2.0*Random()-1.0,2.0*Random()-1.0);
glEnd();
// [multiply resiolution render pass]
static bool _init=true;
GLuint txrid=0; // texture id
BYTE map[xs*ys*3]; // RGB
// init texture
if (_init) // you should also delte the texture on exit of app ...
{
// create texture
_init=false;
glGenTextures(1,&txrid);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txrid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST); // must be nearest !!!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COPY);
glDisable(GL_TEXTURE_2D);
}
// copy low res screen to CPU memory
glReadPixels(0,0,xs,ys,GL_RGB,GL_UNSIGNED_BYTE,map);
// and then to GPU texture
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txrid);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_RGB, GL_UNSIGNED_BYTE, map);
// set multiplied resolution view
glViewport(0,0,m*xs,m*ys);
glClear(GL_COLOR_BUFFER_BIT);
// render low res screen as texture
glBegin(GL_QUADS);
glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
glEnd();
glDisable(GL_TEXTURE_2D);
glFlush();
SwapBuffers(hdc); // swap buffers only here !!!
}
预览:
我在一些 Intel HD 图形上测试了这个(天知道哪个版本)我可以随意使用并且它有效(而标准渲染到纹理方法不是)。
我想制作一个内部分辨率为 320x240 的游戏,但以其整数倍(640x480、960,720 等)呈现到屏幕上。我想要复古的 2D 像素图形。
我通过 glOrtho() 设置内部分辨率实现了这一点:
glOrtho(0, 320, 240, 0, 0, 1);
然后我将输出分辨率放大 3 倍,如下所示:
glViewport(0,0,960,720);
window = SDL_CreateWindow("Title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 960, 720, SDL_WINDOW_OPENGL);
我画的矩形是这样的:
glBegin(GL_LINE_LOOP);
glVertex2f(rect_x, rect_y);
glVertex2f(rect_x + rect_w, rect_y);
glVertex2f(rect_x + dst_w, dst_y + dst_h);
glVertex2f(rect_x, rect_y + rect_h);
glEnd();
它在 320x240(未缩放)下完美运行:
当我放大到 960x720 时,像素渲染一切正常!然而,GL_Line_Loop 似乎不是在 320x240 canvas 上绘制并按比例放大,而是在最终的 960x720 canvas 上绘制。结果是 3px 世界中的 1px 线:(
如何绘制线条到 320x240 glOrtho canvas,而不是 960x720 输出 canvas?
没有“320x240 glOrtho canvas”。只有 window 的实际分辨率:960x720。
您所做的只是放大您渲染的图元的坐标。因此,您的代码表示要呈现从 (20, 20) 到 (40, 40) 的一行。 OpenGL(最终)在每个维度上将这些坐标缩放 3:(60, 60) 和 (120x120)。
但这只是处理端点。中间发生的事情仍然基于您以 window 的实际分辨率渲染的事实。
即使您使用 glLineWidth
来更改线条的宽度,那也只会固定线条宽度。它不会解决线的光栅化基于您渲染的实际分辨率这一事实。所以对角线不会有你可能想要的像素化外观。
正确执行此操作的唯一方法就是正确执行此操作。渲染为实际 320x240 的图像,然后将其绘制为 window 的实际分辨率。
您必须创建该尺寸的纹理,然后将其附加到 framebuffer object。绑定用于渲染的 FBO 并对其进行渲染(将视口设置为图像的大小)。然后取消绑定 FBO,并将该纹理绘制到 window(视口设置为 window 的分辨率)。
正如我在评论中提到的 Intel OpenGL 驱动程序在直接渲染到纹理时存在问题,我不知道有任何可行的解决方法。在这种情况下,唯一的解决方法是使用 glReadPixels
将屏幕内容复制到 CPU 内存中,然后将其复制回 GPU 作为纹理。当然,这比直接渲染到纹理要慢得多。所以这是交易:
设置低分辨率视图
不要只更改 window 的
glViewport
值的分辨率。然后以低分辨率渲染你的场景(只占屏幕的一小部分space)将渲染的屏幕复制到纹理中
- 设置目标分辨率视图
渲染纹理
不要忘记使用
GL_NEAREST
过滤器。最重要的是你只能在此之后而不是之前交换缓冲区!否则会出现闪烁。
此处为 C++ 源代码:
void gl_draw()
{
// render resolution and multiplier
const int xs=320,ys=200,m=2;
// [low res render pass]
glViewport(0,0,xs,ys);
glClearColor(0.0,0.0,0.0,1.0);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
// 50 random lines
RandSeed=0x12345678;
glColor3f(1.0,1.0,1.0);
glBegin(GL_LINES);
for (int i=0;i<100;i++)
glVertex2f(2.0*Random()-1.0,2.0*Random()-1.0);
glEnd();
// [multiply resiolution render pass]
static bool _init=true;
GLuint txrid=0; // texture id
BYTE map[xs*ys*3]; // RGB
// init texture
if (_init) // you should also delte the texture on exit of app ...
{
// create texture
_init=false;
glGenTextures(1,&txrid);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txrid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST); // must be nearest !!!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COPY);
glDisable(GL_TEXTURE_2D);
}
// copy low res screen to CPU memory
glReadPixels(0,0,xs,ys,GL_RGB,GL_UNSIGNED_BYTE,map);
// and then to GPU texture
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,txrid);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_RGB, GL_UNSIGNED_BYTE, map);
// set multiplied resolution view
glViewport(0,0,m*xs,m*ys);
glClear(GL_COLOR_BUFFER_BIT);
// render low res screen as texture
glBegin(GL_QUADS);
glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
glEnd();
glDisable(GL_TEXTURE_2D);
glFlush();
SwapBuffers(hdc); // swap buffers only here !!!
}
预览:
我在一些 Intel HD 图形上测试了这个(天知道哪个版本)我可以随意使用并且它有效(而标准渲染到纹理方法不是)。