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

时间:2013-09-01 10:31:58

标签: ios opengl-es fonts core-animation core-text

OpenGL字体渲染有很多问题,其中很多都是纹理图集(快速但错误)或字符串纹理(仅限固定文本)。

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

What is state-of-the-art for text rendering in OpenGL as of version 4.1?

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

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


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

  1. 我们如何使用字体呈现文字?
  2. 我们如何显示输出?
  3. 对于1(如何渲染),Apple提供了许多方法来获得“正确的”渲染输出 - 但“简单”的方法不支持OpenGL(也许其他一些人也可以 - 例如,有一种简单的方法可以将CoreText输出映射到OpenGL?)。

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

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

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

      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. NB:他们不一样!他们如何呈现文字的细微差别
        3. NSAttributedString,呈现给上述之一
          1. 再次:呈现不同(我所知道的差异相当微妙并被归类为“错误”,各种各样的SO问题)
        4. CATextLayer
          1. iOS字体与旧C渲染之间的混合体。使用“不完全”免费桥接的CFFont / UIFont,它揭示了一些更多的渲染差异/陌生感。
        5. CoreText
          1. ......最终解决方案?但是它自己的野兽......

3 个答案:

答案 0 :(得分:7)

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

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


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


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

简短的回答是:是的,你可以做到,如果你这样做,它就可以了:

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

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

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。 rectmabstring按CoreText计算。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。使用OpenGL Texture创建MTLTextureUIImagerawData即可。

实施例,

到OpenGL纹理:Convert an UIImage in a texture

设置纹理:

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)