OpenGLES 2中的文本/字体渲染(iOS – CoreText?) – 选项和最佳实践?

关于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. 这是错误的,除非你使用20世纪80年代的“位图字体”(即便如此:纹理图集需要比看似更多的工作,如果你需要它更正确的非平凡字体)
    2. (字体不是“字形集合”,有大量的定位,布局,包装,间距,字距,样式,着色,加权等。纹理图集失败)
  2. 修复字符串(使用任何Apple类正确渲染,然后截取背景图像数据,并上传为纹理)
    1. 从人的角度来说,这很快。 在帧渲染中,这非常非常慢。 如果您使用大量更改文本执行此操作,则帧速率会在场内进行
    2. 从技术上讲,它大多是正确的(不完全是:你以这种方式丢失了一些信息),但效率极低

我也看到了,但是听到了好的和坏的事情:

  1. Imagination / PowerVR“Print3D”(链接坏了)(来自制造GPU的人!但是他们的网站移动/移除了文本渲染页面)
  2. FreeType(需要预处理,解释,大量代码,额外的库?)
  3. …和/或FTGL http://sourceforge.net/projects/ftgl/ (谣言:慢?错误?很长时间没有更新?)
  4. Font-Stash http://digestingduck.blogspot.co.uk/2009/08/font-stash.html (高质量,但非常慢?)
  5. 1。

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

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

我做了一些更多的实验,看起来CoreText在与纹理图集和Valve的有符号差异纹理(可以将位图字形转换为独立于分辨率的高分辨率纹理)结合时可能会提供完美的解决方案。

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


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


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

简短的回答是:是的,你可以做到,如果你:

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

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

这很有效,它很快,适用于所有字体,它可以获得所有字体布局和字距调整等。

我知道这篇文章很老了,但是我试图在我的应用程序中完成这个时遇到它。 在我的搜索中,我遇到了这个示例项目

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

使用纹理图谱和签名距离字段技术,它是使用OpenGL完美实现CoreText。 它极大地帮助我实现了我想要的结果。 希望这有助于其他人。

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。 rect通过CoreText从mabstring计算。 CTFramesetterSuggestFrameSizeWithConstraints

 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 。 使用rawData创建OpenGL TextureMTLTextureUIImage是可以的。


例,

到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)