c++ - 如何使用 tilemap 将纹理坐标完美映射到 tile?

标签 c++ opengl texture-mapping

我无法在四边形上完美映射图 block ,我所说的完美是指它将仅渲染纹理图像中特定图 block 的像素,仅此而已。

编辑:(稍微更新了代码)

我现在已经制作了示例工作代码:

/*
Image of the bug: /image/Drb5U.png
edit: the bug seems to change on different gfx cards, but still visible one way or another!
*/
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "Glu32.lib")

#include <windows.h>
#include <stdio.h>
#include <gl/glew.h>
#include <gl/gl.h>

HDC hDC = NULL;
HGLRC hRC = NULL;
HWND hWnd = NULL;
HINSTANCE hInstance;

bool active = 1;
int window_width = 640;
int window_height = 480;

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch(uMsg){
        case WM_ACTIVATE: {
            active = !HIWORD(wParam);
            return 0;
        }
        case WM_CLOSE: {
            exit(0);
            return 0;
        }
        case WM_SIZE: {
            window_width = LOWORD(lParam);
            window_height = HIWORD(lParam);
            return 0;
        }
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

BOOL create_window(int width, int height){
    GLuint PixelFormat;
    WNDCLASS wc;
    DWORD dwExStyle,dwStyle;
    RECT WindowRect;
    WindowRect.left = (long)0;
    WindowRect.right = (long)width;
    WindowRect.top = (long)0;
    WindowRect.bottom = (long)height;
    hInstance = GetModuleHandle(NULL);
    wc.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = TEXT("test");
    if(!RegisterClass(&wc)) return FALSE;
    dwExStyle = WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;
    dwStyle = WS_OVERLAPPEDWINDOW;
    ShowCursor(TRUE);
    AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
    if(!(hWnd = CreateWindowEx(dwExStyle,TEXT("test"),TEXT("test"),dwStyle|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,0,0,width,height,NULL,NULL,hInstance,NULL))) return FALSE;
    static PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR),1,PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,PFD_TYPE_RGBA,(BYTE)32,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,PFD_MAIN_PLANE,0,0,0,0};
    if(!(hDC = GetDC(hWnd))) return FALSE;
    if(!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) return FALSE;
    if(!SetPixelFormat(hDC, PixelFormat, &pfd)) return FALSE;
    if(!(hRC = wglCreateContext(hDC))) return FALSE;
    if(!wglMakeCurrent(hDC, hRC)) return FALSE;
    ShowWindow(hWnd, SW_SHOW);
    SetForegroundWindow(hWnd);
    SetFocus(hWnd);
    return TRUE;
}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
    if(!create_window(window_width,window_height)){
        return 1;
    }
    glShadeModel(GL_SMOOTH);
    glDisable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glClearColor(0,0,0,1);
    glColor4f(1,1,1,1);

    // create red/yellow checkers pattern with slightly randomized tile pixels:
    int img_w = 256;
    int img_h = 256;
    int tilesize = 16;
    unsigned int *data = (unsigned int *)malloc(img_w*img_h*4);
    unsigned int colors[] = {0xFF3333, 0xFFFF33};
    for(int y = 0; y < img_h; y+=tilesize){
        for(int x = 0; x < img_w; x+=tilesize){
            unsigned int i = ((x/tilesize)+((y/tilesize)%2))%2;
            for(int yy = 0; yy < tilesize; yy++){
                for(int xx = 0; xx < tilesize; xx++){
                    int r = ((colors[i]>>0)&255)-(rand()%30);
                    int g = ((colors[i]>>8)&255)-(rand()%30);
                    int b = ((colors[i]>>16)&255)-(rand()%30);
                    if(r < 0) r = 0;
                    if(g < 0) g = 0;
                    if(b < 0) b = 0;
                    data[(y+yy)*img_w+(x+xx)] = (b << 16) | (g << 8) | r;
                }
            }
        }
    }
    // take one tile somewhere from middle and make texcoords for it:
    int x = 2*tilesize;
    int y = 4*tilesize;
    float tx1 = (float)x/img_w;
    float ty1 = (float)y/img_h;
    float tx2 = (float)(x+tilesize)/img_w;
    float ty2 = (float)(y+tilesize)/img_h;

    unsigned int texid = 0;
    glGenTextures(1, &texid);
    glBindTexture(GL_TEXTURE_2D, texid);
    glEnable(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img_w, img_h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data);
    free(data);

    BOOL done = 0;
    static MSG msg;

    float zpos = 600.0f;

    while(!done){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            if(msg.message == WM_QUIT){
                done = TRUE;
            }else{
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }else if(active){
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glViewport(0, 0, window_width, window_height);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            gluPerspective(45.0f, (GLdouble)window_width/(GLdouble)window_height, 0.1f, 5000.0f);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            glRotatef(0, 1,0,0);
            glRotatef(0, 0,1,0);
            glRotatef(0, 0,0,1);
            glTranslatef(0, 0, -zpos);
            zpos /= 1.02f;
            if(zpos < 0.2f) zpos = 0.2f;

            // draw something...
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_BLEND);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, texid);
            glColor4f(1,1,1,1);
            glBegin(GL_QUADS);
                glTexCoord2f(tx1,ty1); glVertex2f(-10,-10);
                glTexCoord2f(tx2,ty1); glVertex2f(0,-10);
                glTexCoord2f(tx2,ty2); glVertex2f(0,0);
                glTexCoord2f(tx1,ty2); glVertex2f(-10,0);
            glEnd();
            glBegin(GL_QUADS);
                glTexCoord2f(tx1,ty1); glVertex2f(0,0);
                glTexCoord2f(tx2,ty1); glVertex2f(10,0);
                glTexCoord2f(tx2,ty2); glVertex2f(10,10);
                glTexCoord2f(tx1,ty2); glVertex2f(0,10);
            glEnd();

            Sleep(16); // dont run too fast or you break your legs!

            SwapBuffers(hDC);
        }
    }

    return 0;
}

还有一个包含多个图 block 的代码(在缩小时看到相同的错误):

/*
Image of the bug: /image/Drb5U.png
edit: the bug seems to change on different gfx cards, but still visible one way or another!
*/
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "Glu32.lib")

#include <windows.h>
#include <stdio.h>
#include <gl/glew.h>
#include <gl/gl.h>

HDC hDC = NULL;
HGLRC hRC = NULL;
HWND hWnd = NULL;
HINSTANCE hInstance;

bool active = 1;
int window_width = 1024;
int window_height = 768;

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch(uMsg){
        case WM_ACTIVATE: {
            active = !HIWORD(wParam);
            return 0;
        }
        case WM_CLOSE: {
            exit(0);
            return 0;
        }
        case WM_SIZE: {
            window_width = LOWORD(lParam);
            window_height = HIWORD(lParam);
            return 0;
        }
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

BOOL create_window(int width, int height){
    GLuint PixelFormat;
    WNDCLASS wc;
    DWORD dwExStyle,dwStyle;
    RECT WindowRect;
    WindowRect.left = (long)0;
    WindowRect.right = (long)width;
    WindowRect.top = (long)0;
    WindowRect.bottom = (long)height;
    hInstance = GetModuleHandle(NULL);
    wc.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = TEXT("test");
    if(!RegisterClass(&wc)) return FALSE;
    dwExStyle = WS_EX_APPWINDOW|WS_EX_WINDOWEDGE;
    dwStyle = WS_OVERLAPPEDWINDOW;
    ShowCursor(TRUE);
    AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
    if(!(hWnd = CreateWindowEx(dwExStyle,TEXT("test"),TEXT("test"),dwStyle|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,0,0,width,height,NULL,NULL,hInstance,NULL))) return FALSE;
    static PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR),1,PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,PFD_TYPE_RGBA,(BYTE)32,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,PFD_MAIN_PLANE,0,0,0,0};
    if(!(hDC = GetDC(hWnd))) return FALSE;
    if(!(PixelFormat = ChoosePixelFormat(hDC, &pfd))) return FALSE;
    if(!SetPixelFormat(hDC, PixelFormat, &pfd)) return FALSE;
    if(!(hRC = wglCreateContext(hDC))) return FALSE;
    if(!wglMakeCurrent(hDC, hRC)) return FALSE;
    ShowWindow(hWnd, SW_SHOW);
    SetForegroundWindow(hWnd);
    SetFocus(hWnd);
    return TRUE;
}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
    if(!create_window(window_width,window_height)){
        return 1;
    }
    glShadeModel(GL_SMOOTH);
    glDisable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glClearColor(0,0,0,1);
    glColor4f(1,1,1,1);

    // create red/yellow checkers pattern with slightly randomized tile pixels:
    int img_w = 256;
    int img_h = 256;
    int tilesize = 16;
    unsigned int *data = (unsigned int *)malloc(img_w*img_h*4);
    unsigned int colors[] = {0xFF3333, 0xFFFF33};
    for(int y = 0; y < img_h; y+=tilesize){
        for(int x = 0; x < img_w; x+=tilesize){
            unsigned int i = ((x/tilesize)+((y/tilesize)%2))%2;
            for(int yy = 0; yy < tilesize; yy++){
                for(int xx = 0; xx < tilesize; xx++){
                    int r = ((colors[i]>>0)&255)-(rand()%30);
                    int g = ((colors[i]>>8)&255)-(rand()%30);
                    int b = ((colors[i]>>16)&255)-(rand()%30);
                    if(r < 0) r = 0;
                    if(g < 0) g = 0;
                    if(b < 0) b = 0;
                    data[(y+yy)*img_w+(x+xx)] = (b << 16) | (g << 8) | r;
                }
            }
        }
    }

    unsigned int texid = 0;
    glGenTextures(1, &texid);
    glBindTexture(GL_TEXTURE_2D, texid);
    glEnable(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img_w, img_h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data);
    free(data);

    BOOL done = 0;
    static MSG msg;

    float zpos = 600.0f;

    while(!done){
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            if(msg.message == WM_QUIT){
                done = TRUE;
            }else{
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }else if(active){
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glViewport(0, 0, window_width, window_height);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            gluPerspective(45.0f, (GLdouble)window_width/(GLdouble)window_height, 0.05f, 5000.0f);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            glRotatef(0, 1,0,0);
            glRotatef(0, 0,1,0);
            glRotatef(0, 0,0,1);
            glTranslatef(0, 0, -zpos);
            zpos /= 1.02f;
            if(zpos < 0.1f) zpos = 0.1f;

            // draw map from randomly picked tiles:
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_BLEND);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, texid);
            glColor4f(1,1,1,1);
            static GLuint my_list = 0;
            static bool list_made = 0;
            if(!list_made){
                int map_w = 100;
                int map_h = 100;
                int tiles_x = img_w/tilesize;
                int tiles_y = img_h/tilesize;
                my_list = glGenLists(1);
                glNewList(my_list, GL_COMPILE);
                glBegin(GL_QUADS);
                for(int y = -map_h; y < map_h; y++){
                    for(int x = -map_w; x < map_w; x++){
                        int xt = (rand()%tiles_x)*tilesize;
                        int yt = (rand()%tiles_y)*tilesize;
                        float tx1 = (float)xt/img_w;
                        float ty1 = (float)yt/img_h;
                        float tx2 = (float)(xt+tilesize)/img_w;
                        float ty2 = (float)(yt+tilesize)/img_h;
                        float x1 = (float)x;
                        float y1 = (float)y;
                        float x2 = (float)(x+1);
                        float y2 = (float)(y+1);
                        glTexCoord2f(tx1,ty1); glVertex2f(x1,y1);
                        glTexCoord2f(tx2,ty1); glVertex2f(x2,y1);
                        glTexCoord2f(tx2,ty2); glVertex2f(x2,y2);
                        glTexCoord2f(tx1,ty2); glVertex2f(x1,y2);
                    }
                }
                glEnd();
                glEndList();
                list_made = 1;
            }
            if(list_made){
                glCallList(my_list);
            }


            Sleep(16); // dont run too fast or you break your legs!

            SwapBuffers(hDC);
        }
    }

    return 0;
}

这也是这个错误的原因:

enter image description here

在上图中,我创建了一个红色/黄色瓷砖的跳棋图案。

我可以通过减少 tx2 和 ty2 纹理坐标的 0.0001f 来解决这个问题,但这是 Not Acceptable ,因为图 block 不再是正确的尺寸,而且它仍然不能完全解决问题。

注意:错误仅出现在一个角落(在本例中为右上角)。

我正在寻找的是渲染一个图 block ,并 100% 确定除了我想要的图 block 之外,不会从该纹理渲染任何其他内容。

  • 真的不行吗,这在opengl中正常吗?
  • 它是否可以通过着色器修复?

我知道如何解决这个问题的唯一方法是用与瓷砖边缘相同的颜色填充瓷砖边缘。或者创建 256 张 16x16 大小的单独图像,这是 Not Acceptable 。

编辑: 我用我的另一台电脑测试了代码:它也有同样的错误,但不同的是:它只在四边形的顶部显示问题,而且只在顶部!并且它在整个时间内也只有 1 个像素厚(并且从一开始就可见,而在我的另一台计算机上则不是)。在我的另一台电脑上,错误出现在顶部和右侧,所以我相信这是一个 gfx 卡/驱动程序问题......我没有更多的电脑来测试它,我相信错误以某种方式可见在任何计算机中。如果有人可以测试代码,而您没有看到错误,请告诉您的 gfx 卡型号和驱动程序版本。

最佳答案

这是基于提供的新示例。请注意,前面所述的信息仍然是正确的,因为亚像素精度是一个真正的问题(这就是我将其留在那里的原因)。然而,这不是新案例中在达到这些子像素限制之前就出现随机线条的原因。

较低缩放级别的线是由纹理坐标插值中的简单不精确引起的。你要传递的是一个半开范围:[0/w, 16/w)。但是如果插值器恰好产生 16/w,那么它将从第 16 个纹素获取。

典型的解决方案是对纹理坐标应用一个小的偏差:

float tx2 = ((float)(xt+tilesize)/img_w) - (1.0f / 8192.0f);
float ty2 = ((float)(yt+tilesize)/img_h) - (1.0f / 8192.0f);

这也将消除子像素问题,因为您正在对纹理坐标应用偏差。

它不会“像素完美”,但如果它是第一次像素完美的,您就不会看到这个问题。在处理缩放和纹理时,像素完美总是相对的。

它将在指定的偏差内实现像素完美。这是 8192 中的 1。鉴于您的情况是 256x256 图像,在像素的 1/32 内是像素完美的。您必须将图 block 放大 32 倍才能缩小一个像素

如果您无需将其带入图像编辑或处理应用程序或通过计算像素就能分辨出差异,我会感到非常惊讶)。

如果您想要更大的图像,您可能需要更小的偏差。请注意,偏差本身的有效性是绝对的;偏差越小,再次看到问题的机会就越大。例如,在 1/32768 的偏差下,我的 Radeon HD-3300 显示了这个问题。所以 8192 非常接近分母可以得到的最大值。

请注意,填充每个图 block 的左上角也可以。就个人而言,我会坚持使用偏差,除非您有太 multimap block 以至于偏差会影响图像的渲染。

请注意,着色器提供了在不修改纹理坐标的情况下解决此问题的方法,只需将纹理坐标作为像素坐标传递,并将它们固定在边缘之前。这适用于任何纹理分辨率。数组纹理可以通过将每个图 block 放在数组中它自己的条目中来工作。

所以这些是您的选择。


注意:我运行的是 AMD HD-3300 卡。

当非常接近对象时,似乎正在发生的事情是纹理获取单元似乎用完了子像素位的精度。我可以这么说,因为如果我将缩放停止的阈值更改为它刚好发生的点,我可以扩大窗口的大小,然后它就会出现。因此,影响效果的是明显的屏幕尺寸。

我能够通过将纹理的大小增加到 4096 来排除纹理坐标插值精度问题。这对问题出现的时间没有任何影响。所以它必须在纹理获取单元硬件本身,而不是纹理坐标插值单元中。也就是说,插值纹理坐标很好;当硬件进行数学运算以获取纹素时,事情就会出错。

经过一些实验,问题似乎出现在纹理元素被映射到大约 64-90 屏幕像素的大小时。因此,在事情变得困惑之前,您似乎获得了大约 6 个子像素位的精度。

关于c++ - 如何使用 tilemap 将纹理坐标完美映射到 tile?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9303284/

相关文章:

3d - "sphere in a bag"平面到球体的投影

c++ - 用于 DOS 的古老 C++ 编译器和 IDE

c++ - 我如何使用 fmod( float )检查 - 并获得余数?

c++ - 如何在 OpenGL(GLUT) 场景中创建静态背景图像?

java - (Java) 将四元数转换为弧度(或度数)?

java - 纹理在 java3d 中无法正确显示

c++ - 为什么 cv::Mat 的 RAM 和硬盘大小总是有 4.5 倍的差异?

C++ CodeBlocks 中类似 PHP 的静态方法

c++ - 使用 VBO 渲染顶点的问题 - OpenGL

c++ - 如何将纹理应用于 OpenGL 中的子网格?