6

想象以下场景:您有一组 PNG 格式的 RPG 角色精灵表,并且您想在 OpenGL 应用程序中使用它们。

单独的字符(通常)大小为 16 x 24 像素(即 24 像素高),并且可以是任何宽度和高度而不会留下填充。有点像这样:

Terra,来自最终幻想VI
(来源:kafuka.org

在给定帧索引和大小的情况下,我已经有了确定基于整数的剪切矩形的代码:

int framesPerRow = sheet.Width / cellWidth;
int framesPerColumn = sheet.Height / cellHeight;
framesTotal = framesPerRow * framesPerColumn;
int left = frameIndex % framesPerRow;
int top = frameIndex / framesPerRow;
//Clipping rect's width and height are obviously cellWidth and cellHeight.

运行此代码frameIndex = 11, cellWidth = 16, cellHeight = 24将返回一个剪贴画(32, 24)-(48, 48),假设它是右/下而不是宽度/高度。

实际问题

现在,给定一个剪切矩形和一个 X/Y 坐标来放置精灵,我如何在 OpenGL 中绘制它?左上角的零坐标是首选。

4

4 回答 4

10

您必须开始考虑坐标在 [0, 1] 范围内的“纹理空间”。

所以如果你有一个精灵表:

class SpriteSheet {
    int spriteWidth, spriteHeight;
    int texWidth, texHeight;

    int tex;

public:
    SpriteSheet(int t, int tW, int tH, int sW, int sH)
    : tex(t), texWidth(tW), texHeight(tH), spriteWidth(sW), spriteHeight(sH)
    {}

    void drawSprite(float posX, float posY, int frameIndex);
};

您所要做的就是将顶点纹理顶点都提交给 OpenGL:

    void SpriteSheet::drawSprite(float posX, float posY, int frameIndex) {
        const float verts[] = {
            posX, posY,
            posX + spriteWidth, posY,
            posX + spriteWidth, posY + spriteHeight,
            posX, posY + spriteHeight
        };
        const float tw = float(spriteWidth) / texWidth;
        const float th = float(spriteHeight) / texHeight;
        const int numPerRow = texWidth / spriteWidth;
        const float tx = (frameIndex % numPerRow) * tw;
        const float ty = (frameIndex / numPerRow + 1) * th;
        const float texVerts[] = {
            tx, ty,
            tx + tw, ty,
            tx + tw, ty + th,
            tx, ty + th
        };

        // ... Bind the texture, enable the proper arrays

        glVertexPointer(2, GL_FLOAT, verts);
        glTexCoordPointer(2, GL_FLOAT, texVerts);
        glDrawArrays(GL_TRI_STRIP, 0, 4);
    }

};
于 2009-10-14T20:07:21.533 回答
7

弗兰克斯的解决方案已经很好了。

只是一个(非常重要的)旁注,因为一些评论另有建议。

请永远不要使用 glBegin/glEnd。永远不要告诉别人使用它。

唯一可以使用 glBegin/glEnd 是在您的第一个 OpenGL 程序中。

数组并不难处理,但是......

  • ...他们更快。
  • ...他们仍然可以使用较新的 OpenGL 版本。
  • ...他们将与 GLES 合作。
  • ...从文件中加载它们要容易得多。
于 2009-10-31T22:54:57.263 回答
0

我假设你正在学习 OpenGL,只需要让它以某种方式工作。如果您需要原始速度,则可以使用着色器和顶点缓冲区以及各种既简洁又复杂的东西。

最简单的方法是将PNG加载到纹理中(假设您有能力将图像加载到内存中,您确实需要htat),然后使用设置适当纹理坐标的四边形绘制它(它们使用浮点坐标从0到1 ,因此您需要相应地除以纹理宽度或高度)。

使用 glBegin(GL_QUADS)、glTexcoord2f()、glVertex2f()、glEnd() 以最简单(但不是最快)的方式绘制它。

为了使左上角为零,要么使用 gluOrtho() 设置与普通 GL 不同的视图矩阵(查找该函数的文档,将 top 设置为 0,bottom 设置为 1 或 screen_height 如果你想要整数坐标)或者只是进行更改您的绘图循环,只需执行 glVertex2f(x/screen_width, 1-y/screen_height)。

有更好更快的方法可以做到这一点,但如果您从头开始学习原始 OpenGL,这可能是最简单的方法之一。

于 2009-10-14T20:02:12.463 回答
0

一个建议,如果可以的话。我使用 SDL 加载我的纹理,所以我所做的是: 1. 我加载了纹理 2. 我确定了如何将 spritesheet 分成单独的 sprite。3. 我将它们分成单独的表面 4. 我为每个表面制作一个纹理(我有一个精灵类来管理它们)。5. 释放表面。这需要更多的时间(显然)加载,但稍后支付。这种方式更容易(也更快),因为您只需计算要显示的纹理的索引,然后显示它。然后,您可以随意缩放/翻译它并调用显示列表以将其呈现为您想要的任何内容。或者,您可以在立即模式下执行此操作,或者都可以:)

于 2009-10-14T20:20:22.467 回答