【LWJGL官方教程】文字

本文档介绍了如何在OpenGL中渲染文本,包括创建字形纹理图集、加载TrueType字体、确定文本的总宽高以及生成和渲染纹理。教程中提到,虽然LWJGL3支持STBTrueType字体类,但官方教程较少,且该方法仅限于ASCII字符,不支持中文。建议读者熟悉纹理教程,并学习如何高效动态地渲染任何文本。
摘要由CSDN通过智能技术生成

原文:https://github.com/SilverTiger/lwjgl3-tutorial/wiki/Fonts
译注:并没有逐字逐句翻译一切,只翻译了自己觉得有用的部分。另外此翻译仅供参考,请一切以原文为准。代码例子文件链接什么的都请去原链接查找。括号里的内容一般也是译注,供理解参考用。总目录传送门:http://blog.csdn.net/zoharwolf/article/details/49472857
译注2:我自己亲自写代码测试时才发现这一篇教程多特么坑爹,仅支持ASCII字符啊亲,作为已经2015年的全新LWJGL3最高性能库是不是这教程的中心思想有点落后啊!!也就是不能搞出中文来。虽然LWJGL3的库里已经有了名为STBTrueType看起来非常高大上的字体类,但是目前官方关于它的教程几乎是0,具体要怎么用还不知。而我能找到的比较方便用的能和LWJGL相容的字体库比如Slick2D的,仅支持LWJGL2,在3里会报错= =不知道怎么办好了,有愿意研究的人去研究吧,或者再等官方更新教程了。

本教程教怎样在OpenGL里画文字,我们将用AWT的Font类读TrueType字体然后动态渲染之。读本教程前最好先看纹理教程

Text rendering in OpenGL OpenGL文字渲染

你可能觉得渲染文字应该是某种很基本的功能,但是其实它是一种进阶功能,并非OpenGL自带基本功能。
有许多方法可以渲染文字,以下是两个常用的:

  • 在BufferedImage中描画文字,然后将其创建为纹理。做起来很简单,但是每句你想写的话都要被创建为一个纹理。
  • 创建一个包含全部字形的纹理图集。这方法略困难,但是只需要这样一个纹理即可动态地写任何文字。

对初学者而言,第一个方法非常直接,但是用它只能搞一些很难变更的静态文本,所以一般只用在那些一次性的文本。
第二种方法更加有效率,所以还是看第二种方法吧。

Creating a glyph texture atlas 创建字形纹理图集

创建每个字形的纹理图集需要几步,首先读取字体,然后决定纹理宽高,最后把字形画在图片上并用它生成纹理。

Loading a TrueType font 读取TrueType字体

在Java里,读字体很直接,用java.awt.Font就可以用InputStream读到字体文件或者简直地用字体名来构造。

java.awt.Font font = new java.awt.Font(MONOSPACED, PLAIN, 16);

用以上代码我们以16号字读取一个默认的等宽字体。MONOSPACED和PLAIN是java.awt.Font里的静态常量。当然你也也可以读其他你想要的字体。
如果你想要读字体文件,那就用java.awt.Font.createFont(fontFormat, fontStream)。
如果用InputStream或java.awt.Font.createFont(fontFormat, fontFile)的话,并且想从File类读取,那fontFormat应该是TRUETYPE_FONT。读一个字体,字号是1,类型是PLAIN。想要更多的字号和类型,应该用deriveFont(style,size)。

Determine total width and height 决定整体的宽高

字体读完,我们来决定字体的宽高,我们需要遍历在我们纹理中需要的ASCII字符。对于标准的字符,我们遍历ASCII字符#32到#256,我们可以忽略ASCII #0到#31,因为它们只是控制符,我们也可以跳过ASCII #127,因为它是删除控制符。最好把字体高度存下来,因为要反复用。

int imageWidth = 0;
int imageHeight = 0;

for (int i = 32; i < 256; i++) {
    if (i == 127) {
        continue;
    }
    char c = (char) i;
    BufferedImage ch = createCharImage(font, c, antiAlias);

    imageWidth += ch.getWidth();
    imageHeight = Math.max(imageHeight, ch.getHeight());
}

fontHeight = imageHeight;

这循环代码很直观,其中重要的部分是写一个createCharImage(font, c antiAlias)方法。
首先想办法得到字体规格。创建一个临时图片并把要画在纹理上的字画在上面。之后只需根据图片创建一个Graphics2D对象,从此对象中可以获得字体规格。

BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
if (antiAlias) {
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
FontMetrics metrics = g.getFontMetrics();
g.dispose();

如果我们想要反走样绘制,只需要设置渲染hint。但是其实这跟你选的字体有关,一些字体不用反走样已经很好了。
根据字体规格,我们就能提取出宽高了。

int charWidth = metrics.charWidth(c);
int charHeight = metrics.getHeight();

之后我们可以按此宽高创建一个新的图片并返回它,此图片上应画上字符。我们用白色来画,之后可以在渲染时再改变。

image = new BufferedImage(charWidth, charHeight, BufferedImage.TYPE_INT_ARGB);
g = image.createGraphics();
if (antiAlias) {
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
g.setFont(font);
g.setPaint(Color.WHITE);
g.drawString(String.valueOf(c), 0, metrics.getAscent());
g.dispose();
return image;

回到最开始我们创建纹理的方法,现在有了字体高度,还有宽度总和,我们可以用此宽高来创建图片了。

BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();

Generate the texture 生成纹理

生成纹理之前,应该创建一个简单字形类,这样就可以保留每个字形的宽高并在纹理中找到它。

public class Glyph {
    public final int width;
    public final int height;
    public final int x;
    public final int y;

    public Glyph(int width, int height, int x, int y) {
        this.width = width;
        this.height = height;
        this.x = x;
        this.y = y;
    }
}

在我们的Font类里,我们需要映射字符到对应的字形上,为此可以建一个Map,你也可以用简单的数组。

private Map<Character, Glyph> glyphs = new HashMap<>();

好极了,我们现在万事俱备,可以创建图集了,跟之前一样,我们遍历标准ASCII字符得到字符图片。还需要有一个创建字形类对象用的偏移值x

if (i == 127) {
    continue;
}
char c = (char) i;
BufferedImage charImage = createCharImage(font, c, antiAlias);

int charWidth = charImage.getWidth();
int charHeight = charImage.getHeight();

用x和字符图片创建字形,要注意,y值取了图片高度减字符高度,这是因为最后我们要将图片垂直翻转以使其原点位于左下角。之后将字符图片画在纹理图片上,随着字符宽度增加偏移值x并将字形放在Map中。

Glyph ch = new Glyph(charWidth, charHeight, x, image.getHeight() - charHeight);
g.drawImage(charImage, x, 0, null);
x += ch.width;
glyphs.put(c, ch);

循环后,用纹理教程中的AffineTransform操作垂直翻转图片,把象素数据放到ByteBuffer里,之后再生成纹理句柄和上传象素数据到GPU。

Rendering the text 渲染文本

为了描画文本,我们用已经填充好的Map取合适的纹理坐标,但是这之前,我们需要知道文本的高度,万一它有许多行呢。既然我们已经知道字体高度,通过遍历字符就很容易计算出来。

int lines = 1;
for(int i = 0; i < text.length(); i++) {
    char ch = text.charAt(i);
    if(char == '\n') {
        lines++;
    }
}
int textHeight = lines * fontHeight;

文本高度很重要,因为习惯上OpenGL用窗口的左下角作为原点,所以为了能以正确的顺序描画每一行,我们必须从最高的位置开始。所以为了在(x,y)位置描画,得检查文本高度是否比字高度更大,那样的话,我们就知道文本不只有一行。

float drawX = x;
float drawY = y;
if(textHeight > fontHeight) {
    drawY += textHeight - fontHeight;
}

现在到了需要注意的部分,决定了渲染过程的坐标之后,我们开始遍历每个字符,并且以储存的字形描画它们。

texture.bind();
renderer.begin();
for (int i = 0; i < text.length(); i++) {
    char ch = text.charAt(i);
    if (ch == '\n') {
        /* Line feed, set x and y to draw at the next line */
        drawY -= fontHeight;
        drawX = x;
        continue;
    }
    if (ch == '\r') {
        /* Carriage return, just skip it */continue;
    }
    Glyph g = glyphs.get(ch);
    renderer.drawTextureRegion(texture, drawX, drawY, g.x, g.y, g.width, g.height, c);
    drawX += g.width;
}
renderer.end();

现在你应该明白,这一切实际上是用批量渲染完成的,我们将在下篇教程里介绍。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值