18

关于 OpenGL 字体渲染有很多问题,其中许多问题都可以通过纹理图集(快速,但错误)或字符串纹理(仅限固定文本)来解决。

但是,这些方法很差,并且似乎已经过时了(使用着色器来更好/更快地做到这一点呢?)。对于 OpenGL 4.1,有一个很好的问题是关于“你今天应该使用什么?”:

从 4.1 版开始,OpenGL 中文本渲染的最新技术是什么?

那么,我们今天应该在 iOS GL ES 2 上使用什么?

我很失望似乎没有开源(甚至商业解决方案)。我知道很多团队都把它搞砸了,花费数周的开发时间重新发明这个轮子,逐渐学习如何调整字距和空间等(呃)——但肯定有比重写整个“字体”更好的方法从头开始?


据我所知,这有两个部分:

  1. 我们如何使用字体渲染文本?
  2. 我们如何显示输出?

对于 1(如何渲染),Apple 提供了许多方法来获得“正确”的渲染输出 - 但“简单”的不支持 OpenGL(也许其他一些支持 - 例如,是否有一种简单的方法来映射 CoreText 输出到OpenGL?)。

对于 2(如何显示),我们有着色器、VBO、字形纹理、查找纹理和其他技术(例如上面链接的 OpenGL 4.1 东西?)

以下是我所知道的两种常见的 OpenGL 方法:

  1. 纹理图集(渲染所有字形一次,然后从共享纹理中为每个字符渲染 1 x 纹理四边形)
    1. 这是错误的,除非您使用的是 1980 年代的“位图字体”(即便如此:纹理图集需要比看起来更多的工作,如果您需要它对非平凡字体正确)
    2. (字体不是“字形的集合”,有大量的定位、布局、环绕、间距、字距调整、样式、着色、加权等。纹理图集失败)
  2. 固定字符串(使用任何 Apple 类正确渲染,然后截取背景图像数据,并作为纹理上传)
    1. 在人类方面,这很快。在帧渲染中,这非常非常慢。如果您使用大量更改的文本执行此操作,则您的帧速率会下降
    2. 从技术上讲,它大部分是正确的(不完全正确:您以这种方式丢失了一些信息)但效率极低

我也见过,但听说过的好事和坏事:

  1. Imagination/PowerVR“Print3D”(链接断开)(来自制造 GPU 的人!但他们的网站已经移动/删除了文本渲染页面)
  2. FreeType(需要预处理、解释、大量代码、额外库?)
  3. ...和/或 FTGL http://sourceforge.net/projects/ftgl/(谣言:慢?有问题?很久没有更新了?)
  4. 字体存储http://digestingduck.blogspot.co.uk/2009/08/font-stash.html(高质量,但很慢?)
  5. 1.

在 Apple 自己的操作系统/标准库中,我知道文本渲染的几个来源。注意:我已经在 2D 渲染项目中详细使用了其中的大部分,我关于它们输出不同渲染的陈述是基于直接经验

  1. 带有 NSString 的 CoreGraphics
    1. 最简单的:渲染“成 CGRect”
    2. 似乎是人们推荐的“固定字符串”方法的一个稍快的版本(即使你希望它大致相同)
  2. 带有纯文本的 UILabel 和 UITextArea
    1. 注意:它们不一样!它们呈现 smae 文本的方式略有不同
  3. NSAttributedString,呈现为上述之一
    1. 再次:呈现方式不同(我所知道的差异相当微妙,被归类为“错误”,关于此的各种 SO 问题)
  4. CATextLayer
    1. iOS 字体和旧 C 渲染的混合体。使用“不完全”的免费桥接 CFFont / UIFont,它揭示了更多的渲染差异/陌生感
  5. 核心文本
    1. ...最终的解决方案?而是自己的野兽……
4

3 回答 3

8

我做了一些更多的实验,似乎 CoreText 与纹理图集和 Valve 的符号差分纹理(可以将位图字形转换为与分辨率无关的高分辨率纹理)结合使用时可能会成为一个完美的解决方案。

...但我还没有工作,还在试验。


更新:Apple 的文档说他们可以让您访问除最终细节之外的所有内容:要渲染的字形 + 字形布局(根据文档,您可以获得行布局和字形的数量,但不能获得字形本身)。没有明显的原因,CoreText 中显然缺少这条核心信息(如果是这样,那 CT 几乎一文不值。我仍在寻找是否能找到一种方法来获取实际的 glpyhs + per-glyph 数据)


UPDATE2:我现在可以在 Apple 的 CT 上正常工作(但没有不同的纹理),但它最终成为 3 个类文件、10 个数据结构、大约 300 行代码,以及渲染它的 OpenGL 代码。SO答案太多了:(。

简短的回答是:是的,你可以做到,而且它有效,如果你:

  1. 创建 CTFrameSetter
  2. 为理论二维帧创建 CTFrame
  3. 创建一个将转换为 GL 纹理的 CGContext
  4. 逐个字形检查,允许 Apple 渲染到 CGContext
  5. 每次 Apple 渲染字形时,计算边界框(这是 HARD),并将其保存在某处
  6. 并保存唯一的字形ID(这对于例如“o”、“f”和“of”(一个字形!)会有所不同)
  7. 最后,将您的 CGContext 作为纹理发送到 GL

渲染时,使用 Apple 创建的字形 ID 列表,并使用保存的信息和纹理来渲染具有纹理坐标的四边形,从而将单个字形从您上传的纹理中拉出。

这行得通,速度很快,它适用于所有字体,它使所有字体布局和字距调整都正确,等等。

于 2013-09-02T20:10:23.650 回答
3

1.

通过创建任何字符串NSMutableAttributedString

let mabstring = NSMutableAttributedString(string: "This is a test of characterAttribute.")
mabstring.beginEditing()
var matrix = CGAffineTransform(rotationAngle: CGFloat(GLKMathDegreesToRadians(0)))
let font = CTFontCreateWithName("Georgia" as CFString?, 40, &matrix)
mabstring.addAttribute(kCTFontAttributeName as String, value: font, range: NSRange(location: 0, length: 4))
var number: Int8 = 2
let kdl = CFNumberCreate(kCFAllocatorDefault, .sInt8Type, &number)!
mabstring.addAttribute(kCTStrokeWidthAttributeName as String, value: kdl, range: NSRange(location: 0, length: mabstring.length))
mabstring.endEditing()

2.

创建 CTFrame。由CoreTextrect计算mabstringCTFramesetterSuggestFrameSizeWithConstraints

let framesetter = CTFramesetterCreateWithAttributedString(mabstring)
let path = CGMutablePath()
path.addRect(rect)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

3.

创建位图上下文。

let imageWidth = Int(rect.width)
let imageHeight = Int(rect.height)
var rawData = [UInt8](repeating: 0, count: Int(imageWidth * imageHeight * 4))
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitsPerComponent = 8
let bytesPerRow = Int(rect.width) * 4
let context = CGContext(data: &rawData, width: imageWidth, height: imageHeight, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: bitmapInfo.rawValue)!

4.

在位图上下文中绘制 CTFrame。

CTFrameDraw(frame, context)

现在,我们得到了原始像素数据rawData。创建OpenGL Texture, MTLTexture,UIImage就可以了rawData


例子,

到 OpenGL 纹理:在纹理中转换 UIImage

设置你的纹理:

GLuint textureID;    
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &textureID);

glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData);

,

//to MTLTexture
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: Int(imageWidth), height: Int(imageHeight), mipmapped: true)
let device = MTLCreateSystemDefaultDevice()!
let texture = device.makeTexture(descriptor: textureDescriptor)
let region = MTLRegionMake2D(0, 0, Int(imageWidth), Int(imageHeight))
texture.replace(region: region, mipmapLevel: 0, withBytes: &rawData, bytesPerRow: imageRef.bytesPerRow)

,

//to UIImage
  let providerRef = CGDataProvider(data: NSData(bytes: &rawData, length: rawData.count * MemoryLayout.size(ofValue: UInt8(0))))
  let renderingIntent =  CGColorRenderingIntent.defaultIntent
  let imageRef = CGImage(width: imageWidth, height: imageHeight, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: bitmapInfo, provider: providerRef!, decode: nil, shouldInterpolate: false, intent: renderingIntent)!
  let image = UIImage.init(cgImage: imageRef)
于 2017-01-23T03:16:43.433 回答
2

我知道这篇文章很旧,但我在尝试在我的应用程序中完全做到这一点时遇到了它。在我的搜索中,我遇到了这个示例项目

http://metalbyexample.com/rendering-text-in-metal-with-signed-distance-fields/

它是使用纹理图集和有符号距离场技术的 OpenGL 的 CoreText 的完美实现。它极大地帮助我达到了我想要的结果。希望这对其他人有帮助。

于 2017-08-23T19:35:07.127 回答