Как идеально отобразить координаты текстуры на плитку с помощью карты тайлов? - PullRequest
1 голос
/ 16 февраля 2012

Я не могу отобразить плитку идеально на четырехугольниках, под идеальным я имею в виду, что она будет отображать только пиксели определенной плитки в текстурном изображении и ничего более.

Редактировать: (немного обновил код)

Я сейчас сделал пример рабочего кода:

/*
Image of the bug: https://i.stack.imgur.com/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.stack.imgur.com/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

На изображении вышеЯ создал шаблон шашек из красных / желтых плиток.

Я могу исправить это, уменьшив 0,0001f из текстовых кодов tx2 и ty2, но это неприемлемо, так как плитки больше не будут иметь правильные размеры, и это все ещене решает проблему полностью.

Примечание: ошибка появляется только в одном углу (в верхнем правом углу в данном случае).

Я ищу этовизуализировать плитку и быть на 100% уверенным, что из этой текстуры не будет визуализировано ничего, кроме плитки, которую я хочу.

  • Разве это не возможно, это нормально в opengl?
  • Может ли это быть исправлено шейдером?

Единственный способ узнать, как это исправить - это дополнитькрая плитки того же цвета, что и края плитки.Или создайте 256 отдельных изображений размером 16x16, что недопустимо.

Редактировать: Я тестировал код на другом компьютере: он тоже имеет ту же ошибку, но другую: толькопоказывает проблему в ТОПе четырехугольника и только вверху!и его толщина ТОЛЬКО 1 пиксель за все время (и видна с самого начала, в то время как на моем другом компьютере его нет).в моем другом компьютере ошибка в верхней и правой части, так что я считаю, что это проблема с картой / драйвером gfx ... У меня больше нет компьютеров для тестирования, и я считаю, что ошибка видна так или иначев любом компьютере.если кто-то может проверить код, но вы не видите ошибку, сообщите модель своей карты gfx и, возможно, версию драйвера.

Ответы [ 2 ]

3 голосов
/ 16 февраля 2012

Это основано на предоставленном новом примере. Обратите внимание, что ранее заявленная информация все еще верна, потому что субпиксельная точность является реальной проблемой (именно поэтому я оставляю ее там). Тем не менее, это , а не причина случайных линий, которые обнаруживаются задолго до достижения этих субпиксельных пределов в новом случае.

Линии с более низким уровнем масштабирования вызваны простой неточностью в интерполяции координат текстуры. То, что вы хотите передать, это полуоткрытый диапазон: [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);

Это также избавит от проблемы с подпикселями, потому что вы применяете смещение к координате текстуры.

Он не будет "идеальным по пикселям", но если бы он был идеальным по пикселям в первый раз, вы бы не увидели проблему. Пиксельное совершенство всегда относительно при масштабировании и текстурах.

Он будет идеальным по пикселям в пределах указанного смещения. Это 1 из 8192. Учитывая ваш случай изображения 256x256, оно идеально подходит для пикселя в пределах 1/32 от пикселя. Вам нужно будет увеличить плитку в 32 раза, прежде чем вы будете даже один пиксель выключен.

Если вы сможете заметить разницу, не принимая ее в приложение для редактирования или обработки изображений или подсчитав количество пикселей, я был бы очень удивлен).

Если вы хотите увеличить изображение, вам может потребоваться меньший уклон. Обратите внимание, что эффективность самого смещения абсолютная ; чем меньше смещение, тем больше шанс увидеть проблему снова. Например, с уклоном 1/32768 моя Radeon HD-3300 показывает проблему. Так что 8192 довольно близко к наибольшему знаменателю.

Обратите внимание, что отступ в верхнем левом углу каждой плитки также будет работать. Лично я бы придерживался смещения, если у вас не так много плиток, что смещение влияет на рендеринг изображения.

Обратите внимание, что шейдеры предоставляют способы решения этой проблемы без изменения координаты текстуры, просто передавая координаты текстуры в виде пиксельной координаты и закрепляя их непосредственно перед краем. Это работает с любым разрешением текстуры. Массивные текстуры будут работать, если поместить каждую плитку в отдельный элемент массива.

Так что это ваши варианты.


Примечание: у меня установлена ​​карта AMD HD-3300.

То, что происходит, когда чрезвычайно близко к объекту, состоит в том, что в модуле выборки текстуры, похоже, заканчивается субпиксельная точность. Я могу сказать, потому что, если я изменю порог, при котором масштабирование останавливается до точки, где он находится на пороге происходящего, я могу увеличить размер окна, и тогда оно появится. Так что видимый размер экрана вызывает эффект.

Мне удалось исключить проблемы точности интерполяции координат текстуры, увеличив размер текстуры до 4096. Это ничего не изменило, когда появилась проблема. Таким образом, это должно быть в самом аппаратном обеспечении блока выборки текстуры, а не в блоке интерполяции координат текстуры. То есть интерполированная текстурная координата в порядке; это когда аппаратные средства делают математику, чтобы заставить texel извлекать данные вещи, которые идут не так.

После некоторых экспериментов похоже, что проблема возникает, когда тексель отображается на размер около 64-90 пикселей экрана. Таким образом, кажется, что вы получите около 6 субпиксельных битов точности, прежде чем все станет грязным.

2 голосов
/ 16 февраля 2012

На изображении выше я создал шашки из красных / желтых плиток.

Не удалось воспроизвести проблему на GTX460 / WinXP, даже если zpos = 0.02f.

Похоже на проблему с драйвером / точностью / фиксированной функцией.

Что мне нужно, так это визуализировать плитку и быть на 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. Потеря мощности графического процессора, корректирующая координаты текстуры с помощью шейдера.

Решение за вами.

...