在 OpenGL 中重用纹理和顶点
Posted
技术标签:
【中文标题】在 OpenGL 中重用纹理和顶点【英文标题】:Reuse texture and vertices in OpenGL 【发布时间】:2019-08-06 09:51:32 【问题描述】:我正在尝试制作一个简单的 2D 游戏,并将世界存储在 Block 的 2D 数组中(一个枚举,每个值都有其纹理)。
由于这些都是简单的不透明图块,因此在渲染时我按纹理对它们进行排序,然后通过转换到它们的坐标来渲染它们。但是,我还需要为我绘制的每个图块指定纹理坐标和顶点,即使它们也是相同的。
这是我目前拥有的:
public static void render()
// Sorting...
for(SolidBlock block : xValues.keySet())
block.getTexture().bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
for(int coordinateIndex = 0; coordinateIndex < xValues.get(block).size(); coordinateIndex++)
int x = xValues.get(block).get(coordinateIndex);
int y = yValues.get(block).get(coordinateIndex);
glTranslatef(x, y, Integer.MIN_VALUE);
// Here I use MIN_VALUE because I'll later have to do z sorting with other tiles
glBegin(GL_QUADS);
loadModel();
glEnd();
glLoadIdentity();
xValues.get(block).clear();
yValues.get(block).clear();
private static void loadModel()
glTexCoord2f(0, 0);
glVertex2f(0, 0);
glTexCoord2f(1, 0);
glVertex2f(1, 0);
glTexCoord2f(1, 1);
glVertex2f(1, 1);
glTexCoord2f(0, 1);
glVertex2f(0, 1);
我想知道是否可以将 loadModel() 放在主循环之前,以避免使用相同的数据加载模型数千次,以及还有什么可以移动以使其尽可能快尽可能!
【问题讨论】:
【参考方案1】:一些快速优化:
glTexParameteri
每个纹理的每个参数只需要调用一次。您应该将它放在加载纹理的代码部分中。
您只需添加更多顶点即可在一对glBegin
/glEnd
中绘制多个四边形。但是,您不能在glBegin
和glEnd
之间进行任何坐标更改(例如glTranslatef
或glLoadIdentity
或glPushMatrix
),因此您必须将x
和y
传递给您的loadModel
函数(为了准确起见,它真的应该被称为addQuad
)。也不允许在 glBegin
/glEnd
之间重新绑定纹理,因此每个纹理必须使用一组 glBegin
/glEnd
。
次要,但不要多次调用xValues.get(block)
,只需在外循环的开头说List<Integer> blockXValues = xValues.get(block)
,然后从那里开始使用blockXValues
。
更多涉及的优化:
旧版 OpenGL 具有绘制列表,它们基本上是 OpenGL 的宏。您可以让 OpenGL 记录您在glNewList
和 glEndList
之间进行的所有 OpenGL 调用(有一些例外),并以某种方式存储它们。下次你想运行这些精确的 OpenGL 调用时,你可以使用glCallList
让 OpenGL 为你做这件事。为了加快后续的抽奖,将对抽奖列表进行一些优化。
纹理切换相对昂贵,您可能已经意识到,因为您按纹理对四边形进行了排序,但有一个比纹理排序更好的解决方案:将所有纹理放入单个 texture atlas。您需要将每个块的子纹理坐标存储在您的SolidBlock
s 中,然后将block
传递给addQuad
,这样您就可以将适当的子纹理坐标传递给glTexCoord2f
。完成后,您不再需要按纹理排序,只需遍历 x 和 y 坐标即可。
良好做法:
每帧仅在绘制过程开始时使用一次glLoadIdentity
。然后使用glPushMatrix
与glPopMatrix
配对来保存和恢复矩阵的状态。这样一来,您的代码的内部部分就不需要知道外部部分事先可能完成或未完成的矩阵变换。
不要使用Integer.MIN_VALUE
作为顶点坐标。使用您自己选择的常数,最好是不会使您的深度范围变大的常数(我假设您正在使用glOrtho
的最后两个参数)。深度缓冲区精度是有限的,如果在将 Z 范围设置为 Integer.MIN_VALUE
到 Integer.MAX_VALUE
后尝试使用 1 或 2 左右的 Z 坐标,则会遇到 Z 冲突问题。此外,您使用的是 float
坐标,因此 int
常量在这里毫无意义。
这是快速通过后的代码(没有更改纹理图集):
private static final float BLOCK_Z_DEPTH = -1; // change to whatever works for you
private int blockCallList;
private boolean regenerateBlockCallList; // set to true whenever you need to update some blocks
public static void init()
blockCallList = glGenLists(1);
regenerateBlockCallList = true;
public static void render()
if (regenerateBlockCallList)
glNewList(blockCallList, GL_COMPILE_AND_EXECUTE);
drawBlocks();
glEndList();
regenerateBlockCallList = false;
else
glCallList(blockCallList);
private static void drawBlocks()
// Sorting...
glPushMatrix();
glTranslatef(0, 0, BLOCK_Z_DEPTH);
for (SolidBlock block : xValues.keySet())
List<Integer> blockXValues = xValues.get(block);
List<Integer> blockYValues = yValues.get(block);
block.getTexture().bind();
glBegin(GL_QUADS);
for(int coordinateIndex = 0; coordinateIndex < blockXValues.size(); coordinateIndex++)
int x = blockXValues.get(coordinateIndex);
int y = blockYValues.get(coordinateIndex);
addQuad(x,y);
glEnd();
blockXValues.clear();
blockYValues.clear();
glPopMatrix();
private static void addQuad(float x, float y)
glTexCoord2f(0, 0);
glVertex2f(x, y);
glTexCoord2f(1, 0);
glVertex2f(x+1, y);
glTexCoord2f(1, 1);
glVertex2f(x+1, y+1);
glTexCoord2f(0, 1);
glVertex2f(x, y+1);
使用现代 OpenGL(顶点缓冲区、着色器和实例化,而不是显示列表、矩阵转换和逐个传递顶点),您会以非常不同的方式解决这个问题,但我会将其保留在我的回答范围之外。
【讨论】:
非常有用和解释性的答案,非常感谢!!以上是关于在 OpenGL 中重用纹理和顶点的主要内容,如果未能解决你的问题,请参考以下文章