像素区域的OpenGL纹理映射

Posted

技术标签:

【中文标题】像素区域的OpenGL纹理映射【英文标题】:OpenGL Texture Mapping by Pixel Region 【发布时间】:2015-01-04 03:42:17 【问题描述】:

假设您有一个简单的游戏 Tileset,如下所示:

现在,当处理一个简单的 GUI 框架(如 .NET)时,加载该图像、选择其中的一部分,然后逐块绘制它会相当容易。但是,当使用 OpenGL 时,这个过程似乎有点……呃,独特的。我可以轻松地将该图像加载到 OpenGL 中并将其绑定到某些几何图形,但是,当涉及到“选择”某些图块(没有解除绑定所述纹理)时,它似乎需要与传统的 x、y、width 不同的数学类型,高度接近。

如果我想为当前图块选择 64,128(像素空间中的坐标)的图块,我会使用反映理想的纹理坐标,而不是我在其他网站上看到人们建议的这些奇怪的分数。

似乎OpenGL在绑定纹理时根本不使用像素空间,或者我可能误解了这里的一些基本概念;我不确定。

这是一种在任意位置渲染 32 x 32 瓦片的简单方法(在本示例中将保持在 0,0):

            int spriteX = 0;
            int spriteY = 0;
            int spriteWidth = 32;
            int spriteHeight = 32;
            GL.BindTexture( TextureTarget.Texture2D , texture );
            GL.Begin( PrimitiveType.Quads );
            
                GL.TexCoord2( 0 , 1 );
                GL.Vertex2( spriteX , spriteY );
                GL.TexCoord2( 1 , 1 );
                GL.Vertex2( spriteX + spriteWidth , spriteY );
                GL.TexCoord2( 1 , 0 );
                GL.Vertex2( spriteX + spriteWidth , spriteY + spriteHeight );
                GL.TexCoord2( 0 , 0 );
                GL.Vertex2( spriteX , spriteY + spriteHeight );
            
            GL.End();

在任何人抱怨即时模式之前:我完全意识到它已被弃用;我不打算将它用于任何类型的成品。

我不会用整个纹理(上面的代码正在做的)映射绘制的四边形,而是告诉它只映射图像的一个区域,比如:X 64,Y 128,宽度 32,高度 32 .

【问题讨论】:

【参考方案1】:

最直接的方法是使用您所说的“这些奇怪的分数”。他们并不是真的那么奇怪。它们只是……分数。

假设您的整个纹理图集是 1024x1024,并且您想要具有指定尺寸(X 64、Y 128、宽度 32、高度 32)的纹理,则纹理坐标为:

left: X / 1024 = 64.0f / 1024.0f
right: (X + Width) / 1024 = 96.f / 1024.0f
top = Y / 1024 = 128.0f / 1024.0f
bottom = (Y + Height) / 1024 = 160.0f / 1024.f

另一种方法是指定纹理坐标的变换,在这种情况下是缩放变换。使用固定管道,它看起来像这样:

glMatrixMode(GL_TEXTURE);
glScalef(1.0f / 1024.0f, 1.0f / 1024.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);

然后您可以使用纹理图集中的像素位置指定纹理坐标。

使用可编程流水线,上述内容已过时。但是您可以通过将统一值传递给着色器来轻松应用相同类型的缩放,然后将其与纹理坐标相乘。

在顶点着色器中,它可能如下所示:

uniform vec2 TexCoordScale;
in vec2 TexCoord;
out vec2 FragTexCoord;
...
    FragTexCoord = TexCoordScale * TexCoord;

然后在片段着色器中,您有匹配的 FragTexCoord in 变量,并将其用于您的纹理采样操作。

在客户端代码中,你设置统一:

GLint texCoordScaleLoc = glGetUniformLocation(program, "TexCoordScale");
glUniform2f(texCoordScaleLoc, 1.0f / 1024.0f, 1.0f / 1024.0f);

并像往常一样为纹理坐标设置顶点属性,只是现在可以使用像素坐标而不是“奇怪的分数”。

【讨论】:

如果我使用缩放转换(像梦一样工作)会有任何明显的性能损失吗? 我怀疑会有可衡量的差异。 @Krythic:您应该只知道纹理矩阵 - 作为矩阵堆栈的其余部分和整个固定函数管道 - 与立即模式一样被弃用。但是,将其移植到可编程着色器是直截了当的——您甚至不需要完整的 4x4 矩阵的开销,一个简单的 2D 向量乘法就足够了。 @derhass 如果你有时间,你认为你可以写一个这样的例子吗?我很感兴趣。 @Krythic:嗯,在什么情况下?您对可编程管道和 GLSL 着色器编程的熟悉程度如何?添加这个比例因子归结为添加uniform vec2 texScale;,然后在对纹理进行采样时通过简单地将texcoords乘以该向量来应用它。在客户端,您只需将制服设置为(1.0/width,1.0/height)。使用相当现代的 GLSL 版本,您还可以直接在着色器中查询纹理大小,这样就可以完全不使用制服。

以上是关于像素区域的OpenGL纹理映射的主要内容,如果未能解决你的问题,请参考以下文章

Opengl 纹理映射和图像分辨率

opengl纹理映射总结

OpenGL的纹理映射问题

opengl中的正交投影和纹理坐标

OpenGL:着色器存储缓冲区映射/绑定

初识OpenGL (-)纹理过滤(Texture Filtering)