[英]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.