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

发布于 2025-01-06 04:45:27 字数 14798 浏览 1 评论 0原文

我无法在四边形上完美映射图块,完美的意思是它只会渲染纹理图像中特定图块的像素,仅此而已。

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

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

/*
Image of the bug: https://i.sstatic.net/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;
}

以及具有多个图块的代码(在较小缩放时看到相同的错误):

/*
Image of the bug: https://i.sstatic.net/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;
}

这也是此错误的内容:

在此处输入图像描述

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

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

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

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

  • 难道真的不可能吗,这在opengl中正常吗?
  • 也许可以通过着色器来修复它吗?

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

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

I am unable to map a tile perfectly on a quad, by perfect i mean that it will render only the pixels from a specific tile in the texture image, and nothing more.

Edit: (updated code a bit)

I have now made example working code:

/*
Image of the bug: https://i.sstatic.net/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;
}

And a code with multiple tiles (to see same bug when less zoom):

/*
Image of the bug: https://i.sstatic.net/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;
}

Here is also what this bug is about:

enter image description here

In the image above i have created a checkers pattern of red/yellow tiles.

I can fix this by reducing 0.0001f from the tx2 and ty2 texcoords, but it is not acceptable since the tiles wont be right sizes anymore, and it still doesn't fix the problem completely.

Note: the error appears only in one corner (top right in this case).

What I'm looking for is to render a tile and be 100% sure that nothing else will be rendered from that texture except the tile i want.

  • Is it really not possible, is this normal in opengl?
  • Could it be fixed by a shader perhaps?

Only way i know how to fix this is to pad the tiles edges with same color as the tile edges are. Or create 256 separate images of 16x16 size, which isn't acceptable.

Edit:
I tested the code with my other computer: it has the same bug too, but different: it only shows the problem at TOP of the quad, and only at the top! and its also ONLY 1 pixel thick for whole time, (and visible from the beginning, while on my other computer its not). in my other computer the error is in top and right side, so i believe its a gfx card/driver problem... I don't have any more computers to test it on, and I believe the error is visible one way or another in any computer. if someone can test the code, and you don't see the error, please tell your gfx card model and maybe driver version.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沉默的熊 2025-01-13 04:45:28

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

较低缩放级别的线条是由纹理坐标插值中的简单不精确引起的。你想要传递的是一个半开范围:[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 像素内是像素完美的。您必须将图块放大 32 倍才能缩小一个像素

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

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

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

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

所以这些就是你的选择。


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

当非常接近物体时,似乎会发生的情况是纹理获取单元似乎耗尽了子像素位的精度。我可以这么说,因为如果我将缩放停止的阈值更改为刚好发生的点,我可以扩大窗口的大小,然后它就会出现。因此,正是屏幕的表观尺寸导致了这种效果。

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

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

This is based on the new example provided. Note that the previously stated information is still correct, because sub-pixel precision is a real issue (which is why I'm leaving it there). However, that's not the cause of the random lines that show up well before reaching those sub-pixel limits in the new case.

The lines at lower zoom levels are caused by simple imprecision in the texture coordinate interpolation. What you want to pass is a half-open range: [0/w, 16/w). But if the interpolator produces exactly 16/w, then it will fetch from the 16th texel.

The typical solution for this is to apply a small bias to the texture coordinate:

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

This will also get rid of the sub-pixel issue, because you're applying a bias to the texture coordinate.

It won't be "pixel perfect", but if it was pixel-perfect the first time, you wouldn't have seen the issue. Pixel-perfection is always relative when dealing with scaling and textures.

It will be pixel-perfect within the bias specified. Which is 1 out of 8192. Given your case of a 256x256, image, that's pixel-perfect within 1/32nd of a pixel. You would have to magnify a tile by 32x before you would be even one pixel off.

If you can tell the difference without taking it into an image editing or processing application, or otherwise by counting pixels, I'd be very surprised).

If you go for bigger images, you may want a smaller bias. Note that the effectiveness of the bias itself is absolute; the smaller the bias, the greater the chance of seeing the problem again. For example, with a bias of 1/32768, my Radeon HD-3300 shows the issue. So 8192 is pretty close to the largest the denominator can get.

Note that padding the top-left of each tile would also work. Personally, I'd stick with the bias unless you have so many tiles that the bias is affecting the rendering of the image.

Note that shaders provide ways to resolve this issue without modifying the texture coordinate, by simply passing the texture coordinates as pixel-coordinate, and clamping them to just before the edge. This works with any texture resolution. Array textures would work by putting each tile in its own entry in the array.

So those are your options.


Note: I'm running an AMD HD-3300 card.

What appears to be happening when extremely close to the object is that the texture fetch unit seems to be running out of sub-pixel bits of precision. I can tell because if I change the threshold at which the zoom stops to the point where it is just on the cusp of happening, I can expand the window's size and then it appears. So it's the apparent screen size that's inducing the effect.

I was able to rule out texture coordinate interpolation precision issues by increasing the size of the texture to 4096. That changed nothing about when the issue appeared. So it must be in the texture fetch unit hardware itself, not the texture coordinate interpolation unit. That is, the interpolated texture coordinate is fine; it's when the hardware does the math to get the texel to fetch from that things go wrong.

After some experimentation, it looks like the issue appears when the texel is being mapped to a size of about 64-90 screen pixels. So it seems you get about 6 sub-pixel bits of precision before things get messy.

扛刀软妹 2025-01-13 04:45:28

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

即使 zpos = 0.02f,也无法在 GTX460/WinXP 上重现该问题。

听起来像是驱动程序/精度/固定功能问题。

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

您可以将每个图块存储在单独的纹理中(我知道这是一种浪费),并使用 GL_CLAMP_TO_EDGE 来表示 GL_TEXTURE_WRAP_S/GL_TEXTURE_WRAP_T

或者,您可以在每个图块周围添加像素边框 - 通过向外复制边缘上的像素来“扩展”图块。即在图块的顶部边缘之上,将顶部边缘的副本放置在左侧边缘的左侧,与图块的右侧和底部边缘相同。这会浪费额外的纹理内存(因为每个图块的宽度/高度都会增加 2 个像素),但你永远不会看到相邻的像素

也许可以通过着色器来修复它吗?

也许。
您可以通过以下方式调整片段着色器中的纹理坐标(非 GLSL 伪代码):

void adjustCoords(float &s, float &t, int tilex, int tiley){
    float sMin = (0.5f + (float)tilex)/(float)image.width,
        sMax = ((float)(tilex + tilewidth) - 0.5f)/(float)image.width,
        tMin = (0.5f + (float)tiley)/(float)image.height,
        tMax = ((float)(tiley + tileheight) - 0.5f)/(float)image.height;
    s = min(max(s, sMin), sMax);
    t = min(max(t, tMin), tMax);
}

在使用它们(纹理坐标)实际从纹理/采样器读取数据之前。但是,您需要知道图块坐标和纹理大小。另一方面,没有什么可以阻止您将此数据编码为顶点属性/制服。

因此,要绕过这个问题,您可以:

  1. 使用更多纹理(切换它们会浪费性能)。
  2. 浪费纹理内存。
  3. 使用着色器调整纹理坐标会浪费 GPU 功率。

决定权在你。

In the image above i have created a checkers pattern of red/yellow tiles.

Could not reproduce the problem on GTX460/WinXP, even with zpos = 0.02f.

Sounds like driver/precision/fixed function problem.

What im looking for is to render a tile and be 100% sure that nothing else will be rendered from that texture except the tile i want.

You could store every tile in separate texture (I know it is a waste) and use GL_CLAMP_TO_EDGE for GL_TEXTURE_WRAP_S/GL_TEXTURE_WRAP_T.

Or you could add border of pixels around every tile - "expand" tile by copying pixels on edges outwards. I.e. above top edge of tile, place copy of top edge, to the left of the left edge, same to the right and bottom edges of the tile. This will waste extra texture memory (since width/height of every tile will increase by 2 pixels), but you'll never see adjacent pixel

Could it be fixed by a shader perhaps?

Perhaps.
You could adjust texture coordinates in a fragment shader this way (non-GLSL pseudocode):

void adjustCoords(float &s, float &t, int tilex, int tiley){
    float sMin = (0.5f + (float)tilex)/(float)image.width,
        sMax = ((float)(tilex + tilewidth) - 0.5f)/(float)image.width,
        tMin = (0.5f + (float)tiley)/(float)image.height,
        tMax = ((float)(tiley + tileheight) - 0.5f)/(float)image.height;
    s = min(max(s, sMin), sMax);
    t = min(max(t, tMin), tMax);
}

before using them (texture coordinates) to actually read data from texture/sampler. However, you'll need to know tile coordinates and texture size. On other hand, nothing prevents you from encoding this data into vertex attributes/uniforms.

So, to bypass the problem, you can:

  1. Use more textures (wasting performance switching them).
  2. Waste texture memory.
  3. Waste GPU power adjusting texture cooridnates using shader.

Decision is yours.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文