[英]How to map texture coordinates perfectly to a tile by using a tilemap?
我無法在四邊形上完美地映射一個瓦片,完美的意思是,它將僅渲染紋理圖像中特定瓦片的像素,僅此而已。
編輯:(更新了一點代碼)
我現在制作了示例工作代碼:
/*
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;
}
以下是此錯誤的含義:
在上圖中,我創建了一個紅色/黃色瓷磚的方格圖案。
我可以通過從tx2和ty2 texcoords減少0.0001f來解決此問題,但是由於瓦片不再是正確的大小,因此這是不可接受的,並且仍然無法完全解決問題。
注意:錯誤僅出現在一個角(在這種情況下為右上角)。
我正在尋找的是渲染圖塊,並確保100%確保除了我想要的圖塊之外,該紋理不會渲染其他任何東西。
我知道如何解決此問題的唯一方法是使用與瓷磚邊緣相同的顏色來填充瓷磚邊緣。 或創建256個16x16大小的單獨圖像,這是不可接受的。
編輯:我在另一台計算機上測試了該代碼:它也具有相同的錯誤,但是不同:它僅在四邊形的TOP處顯示問題,並且僅在頂部顯示! 而且整個時間也只有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之內。 您必須先將圖塊放大32倍,然后才能縮小一個像素 。
如果您能分辨出差異而不將其投入圖像編輯或處理應用程序中,或者通過計數像素來區分差異,我會感到非常驚訝。
如果要獲取更大的圖像,則可能需要較小的偏差。 注意偏差本身的有效性是絕對的 ; 偏差越小,再次看到問題的機會就越大。 例如,在偏差為1/32768的情況下,我的Radeon HD-3300會顯示此問題。 因此8192非常接近分母可以獲得的最大分母。
請注意,也可以填充每個圖塊的左上角。 就個人而言,我會堅持使用這種偏見,除非您有太多的圖塊以致該偏見會影響圖像的渲染。
請注意,着色器通過簡單地將紋理坐標作為像素坐標傳遞並將其鉗位到邊緣之前,提供了無需修改紋理坐標即可解決此問題的方法。 這適用於任何紋理分辨率。 數組紋理可以通過將每個圖塊放在數組中自己的條目中來工作。
這些是您的選擇。
注意:我正在運行AMD HD-3300卡。
當非常接近對象時,似乎發生的情況是紋理獲取單元似乎用完了精度的子像素位。 我可以說出來,因為如果我將縮放停止的閾值更改為正好發生時的臨界點,則可以擴大窗口的大小,然后它就會出現。 因此,產生這種效果的是明顯的屏幕尺寸。
通過將紋理的大小增加到4096,我可以排除紋理坐標插值精度問題。問題出現的時間並沒有改變。 因此它必須在紋理獲取單元硬件本身中,而不是在紋理坐標插值單元中。 也就是說,插值紋理坐標很好; 這是硬件進行數學運算以使texel從錯誤中獲取的時候。
經過一些實驗,當將紋理像素映射到約64-90屏幕像素的大小時,似乎出現了問題。 因此,看起來事情變得混亂之前,您可以獲得大約6個亞像素的精度。
在上圖中,我創建了一個紅色/黃色瓷磚的方格圖案。
即使使用zpos = 0.02f,也無法在GTX460 / WinXP上重現該問題。
聽起來像是驅動程序/精度/固定功能問題。
我正在尋找的是渲染圖塊,並確保100%確保除了我想要的圖塊之外,該紋理不會渲染其他任何東西。
您可以將每個圖塊存儲在單獨的紋理中(我知道這很浪費), GL_CLAMP_TO_EDGE
GL_TEXTURE_WRAP_S
/ GL_TEXTURE_WRAP_T
使用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);
}
在使用它們(紋理坐標)從紋理/采樣器實際讀取數據之前。 但是,您需要了解平鋪坐標和紋理大小。 另一方面,沒有什么可以阻止您將數據編碼為頂點屬性/統一形式。
因此,要繞過該問題,您可以:
決定權屬於您。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.